Use Redux
You can freedomly use Redux which is a very popular tool in React community to solve data management problems for complex projects.
First please install redux 、 react-redux 、 redux-thunk and redux-logger and so on ,such as redux middleware which you need.
$ yarn add redux react-redux redux-thunk redux-logger
# or use npm
$ npm install --save redux react-redux redux-thunk redux-logger
Then you can create a new directory named store under the project src directory, and add an index.js file under the directory to configure the store, set up the middleware of redux according to your preferences. As in the following example, using in the following example redux-thunk and redux-logger these two middleware.
import { createStore, applyMiddleware, compose } from 'redux'
import thunkMiddleware from 'redux-thunk'
import rootReducer from '../reducers'
const composeEnhancers =
typeof window === 'object' &&
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
// Specify extension’s options like name, actionsBlacklist, actionsCreators, serialize...
}) : compose
const middlewares = [
thunkMiddleware
]
if (process.env.NODE_ENV === 'development' && process.env.TARO_ENV !== 'quickapp') {
middlewares.push(require('redux-logger').createLogger())
}
const enhancer = composeEnhancers(
applyMiddleware(...middlewares),
// other store enhancers if any
)
export default function configStore () {
const store = createStore(rootReducer, enhancer)
return store
}
Next, use the Provider component provided in redux to connect the previously written store to the application, in the project entry file app.js.
import React, { Component } from 'react'
import { Provider } from 'react-redux'
import configStore from './store'
import './app.css'
const store = configStore()
class App extends Component {
componentDidMount () {}
componentDidShow () {}
componentDidHide () {}
componentDidCatchError () {}
// The render() function has no practical effect in the App class.
// Please don't modify the function!
render () {
return (
<Provider store={store}>
{this.props.children}
</Provider>
)
}
}
export default App
Then you can start use it. As recommended by redux, you can add
-constants directory, used to store all action type constants
-actions directory, used to store all actions
-reducers directory, used to store all reducers
For example, If we want to develop a simple counter function just contains add and subtract.
Add action type
export const ADD = 'ADD'
export const MINUS = 'MINUS'
Add reducer function
import { ADD, MINUS } from '../constants/counter'
const INITIAL_STATE = {
num: 0
}
export default function counter (state = INITIAL_STATE, action) {
switch (action.type) {
case ADD:
return {
...state,
num: state.num + 1
}
case MINUS:
return {
...state,
num: state.num - 1
}
default:
return state
}
}
import { combineReducers } from 'redux'
import counter from './counter'
export default combineReducers({
counter
})
Add action
import {
ADD,
MINUS
} from '../constants/counter'
export const add = () => {
return {
type: ADD
}
}
export const minus = () => {
return {
type: MINUS
}
}
// Asynchronous action
export function asyncAdd () {
return dispatch => {
setTimeout(() => {
dispatch(add())
}, 2000)
}
}
Finally, we can use it in the page (or component).The connect method provided by redux will commect redux with our page.
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { View, Button, Text } from '@tarojs/components'
import { add, minus, asyncAdd } from '../../actions/counter'
import './index.css'
@connect(({ counter }) => ({
counter
}), (dispatch) => ({
add () {
dispatch(add())
},
dec () {
dispatch(minus())
},
asyncAdd () {
dispatch(asyncAdd())
}
}))
class Index extends Component {
componentWillReceiveProps (nextProps) {
console.log(this.props, nextProps)
}
componentWillUnmount () { }
componentDidShow () { }
componentDidHide () { }
render () {
return (
<View className='index'>
<Button className='add_btn' onClick={this.props.add}>+</Button>
<Button className='dec_btn' onClick={this.props.dec}>-</Button>
<Button className='dec_btn' onClick={this.props.asyncAdd}>async</Button>
<View><Text>{this.props.counter.num}</Text></View>
<View><Text>Hello, World</Text></View>
</View>
)
}
}
export default Index
connect method will receive two parameters: mapStateToProps and mapDispatchToProps.
mapStateToProps, function type, accepts the lateststateas a parameter, which is used to mapstateto componentprops.mapDispatchToProps, function type, receive thedispatch()method and return the callback function expected to be injected into thepropsof the display component.
Hooks
Use Hooks in Redux
The basic setting of using hooks is the same as connect. You need to set up your store and put your application in the Provider component.
使用 hooks 的基本设置和使用 connect 的设置是一样的, 你需要设置你的 store, 并把你的应用放在 Provider 组件中。
const store = configreStore(rootReducer)
class App extends Components {
render () {
return (
<Provider store={store}>
<Index />
</Provider>
)
}
}
In this case, you can use Hooks API provided by redux in function component.
useSelector
const result : any = useSelector(selector : Function, equalityFn? : Function)
useSelector allows you to use selector function to get data from a Redux Store.
The Selector funtion is roughly equivalent to the mapStateToProps parameter of the connect function. It will be called every time the component renders. And it will also subscribe to the Redux store, which will be called when a Redux action is dispatched.
But useSelector is still somewhat different from mapStateToProps:
- Unlike
mapStateToPropswhich only returns objects, the Selector may return any value. - When an action is dispatched,
useSelectorwill make a shallow comparison of the return value before and after the selector. If they are different, the component will be forced to update. - The Selector function does not accept the
ownPropsparameter. But selector can access the props passed down by functional components through closures
Use Case
Basic usage:
import React, { Component } from 'react'
import { useSelector } from 'react-redux'
export const CounterComponent = () => {
const counter = useSelector(state => state.counter)
return <View>{counter}</View>
}
Use the closure to decide how to select data:
export const TodoListItem = props => {
const todo = useSelector(state => state.todos[props.id])
return <View>{todo.text}</View>
}
Advanced Usage
You could learn how to use reselect to cache selector from react-redux documentatio.
useDispatch
const dispatch = useDispatch()
This Hook will return a reference to the dispatch of the Redux store. You can use it to dispatch actions.
Use Case
import React, { Component } from 'react'
import { useDispatch } from 'react-redux'
export const CounterComponent = ({ value }) => {
const dispatch = useDispatch()
return (
<View>
<Text>{value}</Text>
<Button onClick={() => dispatch({ type: 'increment-counter' })}>
Increment counter
</Button>
</View>
)
}
It's recommend to use useCallback to cache the callback, when we use dispatch to pass a callback to child component. Because the component may be re-redered due to changes in references.
// CounterComponent.js
export const CounterComponent = ({ value }) => {
const dispatch = useDispatch()
const incrementCounter = useCallback(
() => dispatch({ type: 'increment-counter' }),
[dispatch]
)
return (
<View>
<Text>{value}</Text>
<MyIncrementButton onIncrement={incrementCounter} />
</View>
)
}
// IncrementButton.js
const MyIncrementButton = ({ onIncrement }) => (
<Button onClick={onIncrement}>Increment counter</Button>
)
export default Taro.memo(MyIncrementButton)
useStore
const store = useStore()
useStore returns a store reference, which is exactly the same as the Provider component reference.
This hook may not be used often, But useSelector is your first choice in most cases, If you need to replace reducers, you may use this API.
Use case
import React, { Component } from 'react'
import { useStore } from 'react-redux'
export const CounterComponent = ({ value }) => {
const store = useStore()
// EXAMPLE ONLY! Do not do this in a real app.
// The component will not automatically update if the store state changes
return <div>{store.getState()}</div>
}