title: 使用 Redux

在 Taro 中可以自由地使用 React 生态中非常流行的数据流管理工具 Redux 来解决复杂项目的数据管理问题。

首先请安装 reduxreact-reduxredux-thunkredux-logger 等一些需要用到的 redux 中间件

  1. $ yarn add redux react-redux redux-thunk redux-logger
  2. # 或者使用 npm
  3. $ npm install --save redux react-redux redux-thunk redux-logger

随后可以在项目 src 目录下新增一个 store 目录,在目录下增加 index.js 文件用来配置 store,按自己喜好设置 redux 的中间件,例如下面例子中使用 redux-thunkredux-logger 这两个中间件

```jsx title=”src/store/index.js” 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 }

  1. 接下来在项目入口文件 `app.js` 中使用 `redux` 中提供的 `Provider` 组件将前面写好的 `store` 接入应用中
  2. ```jsx title="src/app.js"
  3. import React, { Component } from 'react'
  4. import { Provider } from 'react-redux'
  5. import configStore from './store'
  6. import './app.css'
  7. const store = configStore()
  8. class App extends Component {
  9. componentDidMount () {}
  10. componentDidShow () {}
  11. componentDidHide () {}
  12. componentDidCatchError () {}
  13. // 在 App 类中的 render() 函数没有实际作用
  14. // 请勿修改此函数
  15. render () {
  16. return (
  17. <Provider store={store}>
  18. {this.props.children}
  19. </Provider>
  20. )
  21. }
  22. }
  23. export default App

然后就可以开始使用了。如 redux 推荐的那样,可以增加

  • constants 目录,用来放置所有的 action type 常量
  • actions 目录,用来放置所有的 actions
  • reducers 目录,用来放置所有的 reducers

例如我们要开发一个简单的加、减计数器功能

新增 action type

```jsx title=”src/constants/counter.js” export const ADD = ‘ADD’ export const MINUS = ‘MINUS’

  1. 新增 `reducer` 处理
  2. ```jsx title="src/reducers/counter.js"
  3. import { ADD, MINUS } from '../constants/counter'
  4. const INITIAL_STATE = {
  5. num: 0
  6. }
  7. export default function counter (state = INITIAL_STATE, action) {
  8. switch (action.type) {
  9. case ADD:
  10. return {
  11. ...state,
  12. num: state.num + 1
  13. }
  14. case MINUS:
  15. return {
  16. ...state,
  17. num: state.num - 1
  18. }
  19. default:
  20. return state
  21. }
  22. }

```jsx title=”src/reducers/index.js” import { combineReducers } from ‘redux’ import counter from ‘./counter’

export default combineReducers({ counter })

  1. 新增 `action` 处理
  2. ```jsx title="src/actions/counter.js"
  3. import {
  4. ADD,
  5. MINUS
  6. } from '../constants/counter'
  7. export const add = () => {
  8. return {
  9. type: ADD
  10. }
  11. }
  12. export const minus = () => {
  13. return {
  14. type: MINUS
  15. }
  16. }
  17. // 异步的 action
  18. export function asyncAdd () {
  19. return dispatch => {
  20. setTimeout(() => {
  21. dispatch(add())
  22. }, 2000)
  23. }
  24. }

最后,我们可以在页面(或者组件)中进行使用,我们将通过 redux 提供的 connect 方法将 redux 与我们的页面进行连接

```jsx title=”src/pages/index/index.js” 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 ( {this.props.counter.num} Hello, World ) } }

export default Index

  1. `connect` 方法接受两个参数 `mapStateToProps` `mapDispatchToProps`
  2. - `mapStateToProps`,函数类型,接受最新的 `state` 作为参数,用于将 `state` 映射到组件的 `props`
  3. - `mapDispatchToProps`,函数类型,接收 `dispatch()` 方法并返回期望注入到展示组件的 `props` 中的回调方法
  4. ## Hooks
  5. ### 在 Redux 中使用 Hooks
  6. 使用 hooks 的基本设置和使用 `connect` 的设置是一样的,你需要设置你的 `store`,并把你的应用放在 `Provider` 组件中。
  7. ```jsx
  8. const store = configreStore(rootReducer)
  9. class App extends Components {
  10. render () {
  11. return (
  12. <Provider store={store}>
  13. <Index />
  14. </Provider>
  15. )
  16. }
  17. }

在这样的情况下,你就可以使用 redux 提供的 Hooks API 在函数式组件中使用。

useSelector

  1. const result : any = useSelector(selector : Function, equalityFn? : Function)

useSelector 允许你使用 selector 函数从一个 Redux Store 中获取数据。

Selector 函数大致相当于 connect 函数的 mapStateToProps 参数。Selector 会在组件每次渲染时调用。useSelector 同样会订阅 Redux store,在 Redux action 被 dispatch 时调用。

useSelector 还是和 mapStateToProps 有一些不同:

  • 不像 mapStateToProps 只返回对象一样,Selector 可能会返回任何值。
  • 当一个 action dispatch 时,useSelector 会把 selector 的前后返回值做一次浅对比,如果不同,组件会强制更新。
  • Selector 函数不接受 ownProps 参数。但 selector 可以通过闭包访问函数式组件传递下来的 props。

使用案例

基本使用:

  1. import React, { Component } from 'react'
  2. import { useSelector } from 'react-redux'
  3. export const CounterComponent = () => {
  4. const counter = useSelector(state => state.counter)
  5. return <View>{counter}</View>
  6. }

使用闭包决定如何 select 数据:

  1. export const TodoListItem = props => {
  2. const todo = useSelector(state => state.todos[props.id])
  3. return <View>{todo.text}</View>
  4. }

进阶使用

你还可以访问 react-redux 文档 了解如何使用 reselect 缓存 selector。

useDispatch

  1. const dispatch = useDispatch()

这个 Hook 返回 Redux store 的 dispatch 引用。你可以使用它来 dispatch actions。

使用案例

  1. import React, { Component } from 'react'
  2. import { useDispatch } from 'react-redux'
  3. export const CounterComponent = ({ value }) => {
  4. const dispatch = useDispatch()
  5. return (
  6. <View>
  7. <Text>{value}</Text>
  8. <Button onClick={() => dispatch({ type: 'increment-counter' })}>
  9. Increment counter
  10. </Button>
  11. </View>
  12. )
  13. }

当我们使用 dispatch 传递回调到一个子组件时,推荐使用 useCallback 把回调缓存起来,因为组件可能因为引用改变而重新渲染。

  1. // CounterComponent.js
  2. export const CounterComponent = ({ value }) => {
  3. const dispatch = useDispatch()
  4. const incrementCounter = useCallback(
  5. () => dispatch({ type: 'increment-counter' }),
  6. [dispatch]
  7. )
  8. return (
  9. <View>
  10. <Text>{value}</Text>
  11. <MyIncrementButton onIncrement={incrementCounter} />
  12. </View>
  13. )
  14. }
  15. // IncrementButton.js
  16. const MyIncrementButton = ({ onIncrement }) => (
  17. <Button onClick={onIncrement}>Increment counter</Button>
  18. )
  19. export default Taro.memo(MyIncrementButton)

useStore

  1. const store = useStore()

useStore 返回一个 store 引用和 Provider 组件引用完全一致。

这个 hook 可能并不经常使用。useSelector 大部分情况是你的第一选择,如果需要替换 reducers 的情况下可能会使用到这个 API。

使用案例

  1. import React, { Component } from 'react'
  2. import { useStore } from 'react-redux'
  3. export const CounterComponent = ({ value }) => {
  4. const store = useStore()
  5. // EXAMPLE ONLY! Do not do this in a real app.
  6. // The component will not automatically update if the store state changes
  7. return <div>{store.getState()}</div>
  8. }