Welcome to continue reading the Taro Small program Development large-scale Practical series, previously on:
- Familiar React, familiar Hooks: We implemented a very simple prototype for adding posts using React and Hooks
- Multi-page hops and the Taro UI Component Library: We implemented multi-page hops with the built-in Taro routing function, and upgraded the application interface with the Taro UI component library
- Realize wechat and Alipay multi-terminal login: realize wechat, Alipay and ordinary login and logout login
If you follow this, you will find that state management and data flow are becoming more and more bloated, and component state updates are very complicated. In this article, we will start refactoring with Redux. Since this refactoring involves a lot of file changes, we will cover this step with Redux in two articles, this is the first.
If you’re not familiar with Redux, we recommend reading our Redux Tutorial series:
- Redux Redux Redux Redux Redux Redux Redux Redux Redux Redux
- Redux: Strike while the iron is hot and completely refactor
- Redux Education Package Association (3) : To perform their respective duties and regain the original purpose
If you want to start directly from this step, run the following command:
git clone -b redux-start https://github.com/tuture-dev/ultra-club.git
cd ultra-club
Copy the code
The source code for this article is available on Github. If you think we did a good job, please give ❤️ a thumbs up +Github repository + star ❤️
A combination of double swords
The SRC /pages/mine/mine.jsx file is the top component of many states. Such as our common SRC/components/login button LoginButton/index JSX components and our SRC/components/Footer/index. The JSX components, IsOpened is operated in LoginButton, which then affects the FloatLayout popover in Footer component. In this case, multiple sub-components communicate with each other. The way we save state to the common parent is called “state promotion” in React.
However, as the number of states increases, so does the number of states promoted by the state, resulting in bloated parent components that store these states. In addition, each state change needs to affect many intermediate components, bringing great performance overhead. Such a state management problem is usually handled by Redux, a special state management container. React instead focuses on rendering the user interface.
Redux not only ensures the predictability of state, but also ensures that state changes are only relevant to the corresponding component and do not affect irrelevant components. For a detailed tutorial on Redux, see the Redux tutorial series.
In this section, we will combine React Hooks and Redux to refactor our state management.
Install dependencies
First let’s install the necessary dependencies to use Redux:
$ yarn add redux @tarojs/redux @tarojs/redux-h5 redux-logger
Or use NPM
$ npm install --save redux @tarojs/redux @tarojs/redux-h5 redux-logger
Copy the code
In addition to the familiar Redux dependencies and the middleware redux-Logger used to print actions, there are two additional packages. This is because in Taro, Redux’s react-redux library has been replaced with @tarojs/ Redux and @tarojs/redux-h5, the former used in applets and the latter used in H5 pages. Taro encapsulates the original React-Redux and provides a package that is almost identical to the React-Redux API to give developers a better development experience.
Create a Redux Store
The three core concepts of Redux are: Store, Action and Reducers:
- Store: Holds global state and is known as “the only source of truth for data”.
- Action: Initiates the Action to change the saved state in the Store. It is the only means to change the state.
- Reducers: A series of pure functions used to modify the state in the Store in response to actions.
Ok, now that we’ve reviewed the concept of Redux, we’re going to create stores, and Redux best practices recommend that we save stores in the Store folder, we create the Store folder under the SRC folder, And create index.js in it to write our Store:
import { createStore, applyMiddleware } from 'redux'
import { createLogger } from 'redux-logger'
import rootReducer from '.. /reducers'
const middlewares = [createLogger()]
export default function configStore() {
conststore = createStore(rootReducer, applyMiddleware(... middlewares))return store
}
Copy the code
As you can see, we exported a configureStore function and created and returned a Store. Here we use the Redux-Logger middleware to initiate an Action. Print the state information saved in the Action and its surrounding stores on the console.
Here our createstore receives two parameters: rootReducer and applyMiddleware(… Middlewares).
RootReducer is the reducer of the response actions. Here we export a rootReducer that represents all reducer assemblers, which we will discuss in “Combining users and Post Reducer” later.
For the second argument to createStore, we use applyMiddleware, a utility function provided by Redux, to inject middleware into Redux because it receives arguments such as args1, args2, args3… , argsn), so here we use the array expansion operator… To expand the Middlewares array.
Write the User Reducer
After creating the Store, we went to compile the Reducer. Back to our page logic, we have two tabs at the bottom, and one for the “home page”, an “I”, the “home page” in the main is to show a list of articles and allows you to add articles, etc., in the “I” in the main is to allow the user to log in and show the login information, so overall we there are two types of logic, We named them Post and User, respectively, and we’ll create reducers that handle both types of logic.
The logic form of Reducer is (state, action) => newState, that is to receive the last state and the modified state action, and return the newState after modification, which is a pure function. That means we can’t change the state by mutation.
Recommendation:
newState = { ... state,prop: newValue }
Copy the code
Is not recommended:
state.prop = newValue
Copy the code
The best practice recommended by Redux is to create separate Reducers folders where we save our reducer files individually. We created the reducers folder under the SRC folder, created the user.js file in it, and added the corresponding content of our User Reducer as follows:
import { SET_LOGIN_INFO, SET_IS_OPENED } from '.. /constants/'
const INITIAL_STATE = {
avatar: ' '.nickName: ' '.isOpened: false,}export default function user(state = INITIAL_STATE, action) {
switch (action.type) {
case SET_IS_OPENED: {
const { isOpened } = action.payload
return { ...state, isOpened }
}
case SET_LOGIN_INFO: {
const { avatar, nickName } = action.payload
return { ...state, nickName, avatar }
}
default:
return state
}
}
Copy the code
We declared the initial state INITIAL_STATE of the User Reducer in user.js and assigned it to the default value of the user function state, which receives the actions to be responded to, Inside the user function is a switch statement that evaluates action. Type and executes the corresponding logic. Here we have two main types: SET_IS_OPENED is used to change the isOpened property, SET_LOGIN_INFO is used to change the Avatar and nickName properties. If no action. Type value is matched in the switch statement, it returns the original state.
prompt
According to recent Redux practice, the SET_IS_OPENED and SET_LOGIN_INFO constants here are generally saved in the Constants folder, which we will create immediately. The purpose of using constants here rather than hardcoding strings directly is for the maintainability of the code.
Next we will create the constants we will use in SRC /reducer/user.js. We will create the constants folder in SRC and create user.js and add the following:
export const SET_IS_OPENED = 'MODIFY_IS_OPENED'
export const SET_LOGIN_INFO = 'SET_LOGIN_INFO'
Copy the code
Write a Post Reducer
In response to the state modification of the POST logic, we created post.js under SRC /reducers and wrote the following in it:
import { SET_POSTS, SET_POST_FORM_IS_OPENED } from '.. /constants/'
import avatar from '.. /images/avatar.png'
const INITIAL_STATE = {
posts: [{title: 'Telo Altman'.content: "Tiro is the only biological son of the Father and mother of Otter.".user: {
nickName: 'Tufinch sauce',
avatar,
},
},
],
isOpened: false,}export default function post(state = INITIAL_STATE, action) {
switch (action.type) {
case SET_POSTS: {
const { post } = action.payload
return { ...state, posts: state.posts.concat(post) }
}
case SET_POST_FORM_IS_OPENED: {
const { isOpened } = action.payload
return { ...state, isOpened }
}
default:
return state
}
}
Copy the code
It can be seen that the form of Post Reducer is similar to that of User Reducer. We extracted state posts and isOpened that needed to be shared in multiple components before and saved them in the state of Post. The Post function here mainly responds to the SET_POSTS logic. It is used to add new post-to-posts states and SET_POST_FORM_IS_OPENED logic. The user sets isOpened state.
Next we create the constants we will use in SRC /reducer/post.js, we create a user.js file under SRC /constants and add the following:
export const SET_POSTS = 'SET_POSTS'
export const SET_POST_FORM_IS_OPENED = 'SET_POST_FORM_IS_OPENED'
Copy the code
Those of you who are sharp-eyed may have noticed that we import the constants we need to use in SRC /reducers/user.js and SRC /reducers/post.js from.. /constants, that’s because we created an index.js file under the SRC /constants folder to export all constants in a uniform way, which is also an attempt at code maintainability.
export * from './user'
export * from './post'
Copy the code
Combine the User and Post Reducer
We have previously split the whole global response logic into SRC /reducers/user.js and SRC /reducers/post.js respectively, which enables us to split the response logic into many small function units, greatly increasing the readability and maintainability of the code.
Ultimately, we will combine the split logic into a logical tree and pass it as a parameter to the createStore function.
Redux provides combineReducers for us to combine the split logic. We created an index.js file under the SRC /reducers folder and wrote the following in it:
import { combineReducers } from 'redux'
import user from './user'
import post from './post'
export default combineReducers({
user,
post,
})
Copy the code
As you can see, we import user.js and post.js and pass them to the combineReducers function using the object introduction method and export them. CombineReducers combines the logic and exports it to rootReducer as arguments to be used in our SRC /store/index.js createStore function.
The combineReducers function here mainly accomplishes two things:
- Combine the state from the User Reducer and post Reducer, and merge them into a shape as follows
{ user, post }
Of the state tree, whereuser
Property to save the state of the user Reducer,post
Property holds the state of the Post Reducer. - Distribute actions when in the component
dispatch
An Action,combineReducers
The user Reducer and post Reducer are traversed, and a Reducer is matched to any Reducerswitch
Statement, the Action is responded to.
prompt
We’ll explain how to dispatch actions in a component shortly.
Integrate Redux and React
After we wrote the reducers and created the Store, the next step to consider was how to integrate Redux into React. We opened SRC /app.js and made the following changes:
import Taro, { Component } from '@tarojs/taro'
import { Provider } from '@tarojs/redux'
import configStore from './store'
import Index from './pages/index'
import './app.scss'
// ...
const store = configStore()
class App extends Component {
config = {
// ...
}
render() {
return (
<Provider store={store}>
<Index />
</Provider>
)
}
}
Taro.render(<App />, document.getElementById('app'))
Copy the code
As you can see, there are three main changes to the above content:
- We import
configureStore
And call it to getstore
. - Next we take the Taro binding library from Redux
@tarojs/redux
exportProvider
It provides a bridge between Redux and React. - And finally we use
Provider
Wrap our previous root component and willstore
Passed in as its property so that subsequent components can be retrievedstore
The state saved inside.
Action is new from Hooks
With the Store and Reducer in place, and Redux and React integrated, it’s time to experience the power of the Redux state management container, but in order to use the Hooks version of Action, here are the Hooks that will be used.
useDispatch Hooks
This Hooks returns the Dispatch reference to the Redux store. You can use it to dispatch actions.
Finished useDispatch Hooks, we come back to the practice of a wave, first of all we “common login” Redux of the problem, let’s open the SRC/components/LoginButton/index, js, make corresponding change of the content is as follows:
import Taro from '@tarojs/taro'
import { AtButton } from 'taro-ui'
import { useDispatch } from '@tarojs/redux'
import { SET_IS_OPENED } from '.. /.. /constants'
export default function LoginButton(props) {
const dispatch = useDispatch()
return (
<AtButton
type="primary"
onClick={()= >Dispatch ({type: SET_IS_OPENED, payload: {isOpened: true}})} > Normal login</AtButton>)}Copy the code
As you can see, there are four main changes:
- First we start with
@tarojs/redux
exportuseDispatch
API. - Next we export from the constant file we defined earlier
SET_IS_OPENED
Constants. - And then, we were in
LoginButton
Called in a functional componentuseDispatch
Hooks to return oursdispatch
Function that we can use to dispatch actions to modify the state of the Redux store - And finally we are going to
AtButton
的onClick
The received callback function is replaced, and when the button is clicked, we initiate onetype
为SET_IS_OPENED
Action, and passed onepayload
Parameter, used to Redux the correspondinguser
Properties of theisOpened
Modified totrue
.
Fix the “common login”, we went on to tidy up “WeChat login” logic, open the SRC/components/WeappLoginButton/index. The js file, the file content to make the following changes:
import Taro, { useState } from '@tarojs/taro'
import { Button } from '@tarojs/components'
import { useDispatch } from '@tarojs/redux'
import './index.scss'
import { SET_LOGIN_INFO } from '.. /.. /constants'
export default function WeappLoginButton(props) {
const [isLogin, setIsLogin] = useState(false)
const dispatch = useDispatch()
async function onGetUserInfo(e) {
setIsLogin(true)
const { avatarUrl, nickName } = e.detail.userInfo
await Taro.setStorage({
key: 'userInfo'.data: { avatar: avatarUrl, nickName },
})
dispatch({
type: SET_LOGIN_INFO,
payload: {
avatar: avatarUrl,
nickName,
},
})
setIsLogin(false)}// return ...
}
Copy the code
As you can see, the changes above are similar to those made in normal Login:
- We have derived
useDispatch
hook - Derived by
SET_LOGIN_INFO
constant - We then pass in the parent component that was called earlier
setLoginInfo
The method was changed to Dispatchtype
为SET_LOGIN_INFO
Action because of ouravatar
和nickName
State already instore
In theuser
Property, so we also need to modify through the Dispatch action, and finally we define the previous in the parent componentTaro.setStorage
The method of setting the cache is moved to the child components to ensure consistent changes to the information.
We finally to get “pay treasure to log in” the story of logic, open the SRC/components/AlipayLoginButton/index, js to make corresponding change the file content is as follows:
import Taro, { useState } from '@tarojs/taro'
import { Button } from '@tarojs/components'
import { useDispatch } from '@tarojs/redux'
import './index.scss'
import { SET_LOGIN_INFO } from '.. /.. /constants'
export default function AlipayLoginButton(props) {
const [isLogin, setIsLogin] = useState(false)
const dispatch = useDispatch()
async function onGetAuthorize(res) {
setIsLogin(true)
try {
let userInfo = await Taro.getOpenUserInfo()
userInfo = JSON.parse(userInfo.response).response
const { avatar, nickName } = userInfo
await Taro.setStorage({
key: 'userInfo'.data: { avatar, nickName },
})
dispatch({
type: SET_LOGIN_INFO,
payload: {
avatar,
nickName,
},
})
} catch (err) {
console.log('onGetAuthorize ERR: ', err)
}
setIsLogin(false)}// return ...
}
Copy the code
As you can see, the changes above are almost the same as those in “wechat login”, so we won’t repeat the explanation here 🙂
UseSelector Hooks
With all the way down the students may be a bit understand that we are using Redux code before us, and we are the thinking of refactoring is first from SRC/pages/mine/mine. The JSX in SRC/components/headers/index. The JSX began, Fix the Header. The inside of the JSX all login button, the following should turn to the Header. The last component in the JSX SRC/components/LoggedMine/index. The JSX.
Because we need useSelector Hooks in the LoggedMine component, let’s start with the Hooks.
useSelector Hooks
UseSelector allows you to retrieve data from a Redux Store using a selector function.
The Selector function is roughly equivalent to the mapStateToProps parameter of the connect function. Selector is called every time the component is rendered. UseSelector also subscribes to the Redux Store and is called when the Redux action is dispatched.
But useSelector still has some differences from mapStateToProps:
- Don’t like
mapStateToProps
Just like returning an object, Selector might return any value. - When an action Dispatch occurs,
useSelector
It does a shallow comparison of the return values of the selectors before and after, and if they’re different, the component forces an update. - Selector is not accepted
ownProps
Parameters. But a selector can access props passed down from a functional component through a closure.
Good, understand the concept of useSelector, after we come back to the field, open the SRC/components/LoggedMine/index. The JSX file, some content to make the following changes:
import Taro from '@tarojs/taro'
import { View, Image } from '@tarojs/components'
import { useSelector } from '@tarojs/redux'
import { AtAvatar } from 'taro-ui'
import './index.scss'
export default function LoggedMine(props) {
const nickName = useSelector(state= > state.user.nickName)
const avatar = useSelector(state= > state.user.avatar)
function onImageClick() {
Taro.previewImage({
urls: [avatar],
})
}
return( <View className="logged-mine"> {avatar ? ( <Image src={avatar} className="mine-avatar" onClick={onImageClick} /> ) : (<AtAvatar size="large" circle text=" small "/>)} <View className=" mine-nickname ">{nickName}</View> </View>)}Copy the code
As you can see, our code above has four major changes:
- First we start with
@tarojs/redux
The deduceduseSelector
Hooks. - And then we used it twice
useSelector
Retrieved from the Redux StorenickName
和avatar
, they are locatedstate.user
Under the properties. - And then we’re going to go from
props
Student: I got it in therenickName
和avatar
Instead, we get the state from the Redux Store, and here we get it from the user experiencetaro-ui
Export one from theAtAvatar
Components used to display in noavatar
Is the default profile picture. - Finally, click on the avatar to preview
onImageClick
In the method, we use the Redux storeavatar
.
It is time to harvest the last wave “leek”, let’s complete the Header/index of js Redux, open the SRC/components/headers/index. The js, a change to the content of them is as follows:
// ...
import { useSelector } from '@tarojs/redux'
// import various components...
export default function Header(props) {
const nickName = useSelector(state= > state.user.nickName)
// To construct a Boolean value corresponding to the string, which is used to indicate whether the user is logged in
constisLogged = !! nickNameconst isWeapp = Taro.getEnv() === Taro.ENV_TYPE.WEAPP
const isAlipay = Taro.getEnv() === Taro.ENV_TYPE.ALIPAY
return (
<View className="user-box">
<AtMessage />
<LoggedMine />{! isLogged && (<View className="login-button-box">
<LoginButton />
{isWeapp && <WeappLoginButton />}
{isAlipay && <AlipayLoginButton />}
</View>
)}
</View>)}Copy the code
As you can see, there are five major changes to the code above:
- So first we derived it
useSelector
Hooks. - And then we use
useSelector
To get what we neednickName
Property for double-take inversion to a Boolean valueisLogged
“Indicates whether to log in. - We then take what we got earlier from the parent component
props.isLogged
Property is replaced with the new fromisLogged
值 - Next, we remove what is no longer needed on the “Normal Login” button
handleClick
Properties and “wechat Login”, “Alipay login” above are no longer requiredsetLoginInfo
Properties. - And finally, we get rid of
LoggedMine
No longer needed on the componentuserInfo
Property because we already use it inside the componentuseSelector
Hooks are retrieved from inside the component.
summary
In this article, we have covered refactoring the state management of the user logic. Due to space constraints, we have left the Footer part of the user logic to be covered. In the next article, we will first cover refactoring the state management of the Footer component using the Hooks version of Redux. Let’s talk about state management for the POST part of refactoring.
Want to learn more exciting practical skills tutorial? Come and visit the Tooquine community.
The source code for this article is available on Github. If you think we did a good job, please give ❤️ a thumbs up +Github repository + star ❤️