React 学习系列(五): react-redux 深入学习

758 阅读8分钟

概述

Redux 提供了一套通用的 状态管理机制 帮助我们在复杂 react 应用中管理组件中的 公共状态 - state

使用 Redux 时, 我们会先使用 Redux.createStore(reducer) 构建一个 store 对象。通过 store.getState 可以获取 公共状态 - state。 当我们需要修改 state 时,使用 store.dispatch(action) 触发 state 的更新。

要在 react 应用中使用 Redux, 我们还需要 react-redux 的帮助。 通过 react-redux, 我们可以在组件中很方便的访问 store 对象的 state, 并通过 store.dispatch 修改 state,然后通知使用 store.state 的组件更新。

工作流程梳理

使用 react-redux 时, 我们通常会使用 ReactRedux.connect 方法返回的 包装方法展示组件(Presentational Component) 转化为一个 容器组件(Container Component),然后将 容器组件 放在 <ReactRedux.Provider store={store} /> 中, 这样我们就可以在 展示组件 中, 访问 store 对象的 state,并通过 store.dispatch 触发 store.state 的更新。

具体如何在 react 应用中使用 reduxreact-redux,可以参照 官网 - 搭配 React示例 - Todo 列表react-redux 官网

react-redux整个工作流程, 简单来说就是 容器组件 在渲染时通过 mapStateToPropsmapDispatchToProps 获取 指定的 store.statestore.dispatch,然后将 store.statestore.dispatch 通过 props 传递给 子组件 - 展示组件展示组件 就可以利用 store.statestore.dispatch 进行渲染。

容器组件 渲染完成以后, 会将更新过程包装为一个 listener, 通过 store.subscribe 方法添加到 store 对象的 listeners 列表中。 当我们在子组件中通过 dispatch 方法触发 store.state 更新时, 执行 listeners 列表中的 listener, 触发 容器组件 的更新。 容器组件会重新通过 mapStateToProps 获取新的 state,如果和上一次的 state 不一样, 触发 子组件-展示组件 的更新。

具体的工作流程,参照如下流程图:

使用 hooks

除了使用 connect 方法以后, 我们还可以通过使用 react-redux 提供的 hooks - useSelectoruseDispatchreact 应用中使用 redux

具体的用法如下:

function Component(props) {
    const todos = useSelector(state => state.todos)
    const dispatch = useDispatch()
    return (
        <ul>
            {todos.map(todo => (<li>{todo.text}</li>))}  
        </ul>
    )
    
}

通过 useSelectoruseDispatch, 我们可以在组件中获取 store.statestore.dispatch。当 关联的 store.state 发生变化时,组件自动更新。

useSelector 使用时,需要传入一个 selector,用于从 store.state 中获取需要的 state。在执行过程中,会给组件定义一个 更新机制,并包装为一个 listener, 通过 store.subscribe 方法添加到 store 对象的 listeners 列表中。 当我们通过 dispatch 方法触发 store.state 更新时, 会执行 store 对象 listeners 列表中的 listener, 触发组件的更新。

useDispatch 使用时, 会返回一个 store.dispatch 供组件调用。 组件可以调用 dispatch 来更新 store.state

注意: 使用 useSelector、useDispatch 的组件必须放置在 <ReactRedux.Provider store={store} />, 否则无效

hooks 方式仅适用于函数式组件

useSelectoruseDispatch 方法的用法详见官网: react-redux - hooks

常用 API 解析

  • connect

    • 功能

      connect 方法用于将 react 组件和 redux 关联起来。

      connect 方法使用时,会返回一个 包装方法 - wrapWithConnect。使用 wrapWithConnect,可以将 展示组件 转化为 容器组件容器组件 会在渲染过程中从 store 中获取 statedispatch, 然后通过 props 传递给 展示组件 供其使用。

    • 参数

      connect 方法在执行时接收 四个 参数: mapStateToPropsmapDispatchToPropsmergePropsoptions。各个参数的情况如下:

      • mapStateToProps? : Function

        mapStateToPropsconnect 方法执行时传入的 第一个参数,是一个 函数,在 容器组件渲染 时触发,用于从 store 中获取 state,然后通过 props 传递给 子组件 - 展示组件 供其使用。

        定义 mapStateToProps 的时候, 可以有 一个或者两个参数。如果只有一个参数, 那么触发 mapStateToProps 时, 传入 store.state; 如果是两个参数, 触发 mapStateToProps 时, 传入 store.state容器组件的 props

        使用 connect 时, 如果 指定 mapStateToProps, 那么 容器组件及展示组件 就会随着 关联的 store.state 的修改而 自动触发更新

        如果 指定 mapStateToProps, 但是 与容器组件关联的 store.state 没有更新, 容器组件及展示组件 不会触发更新

        如果 未指定mapStateToProps,即 没有传参或者为 null(undefined),容器组件及展示组件 不会触发更新

      • mapDispatchToProps? : Function | Object

        mapDispatchToPropsconnect 方法执行时传入的 第二个参数, 用于将 store.dispatch 通过 props 传递给 子组件 - 展示组件 供其使用。

        mapDispatchToProps 的类型可以是 函数 以及 对象。不同的类型,代表 不同的 dispatch 使用方式

        如果 mapDispatchToPropsreact-redux 会默认将 { dispatch: store.dispatch } 传递给 展示组件, 展示组件通过 props.dispatch 即可触发 store.state 的修改。

        如果 mapDispatchToProps 是一个 对象, 那么对象的属性值必须是 ActionCreator, react-redux 会通过 redux.bindActionCreatorsActionCreatorstore.dispatch 绑定,将 {'xx': bindActionCreator} 传递给展示组件, 展示组件通过 props.xx 使用 bindActionCreator 来触发 store.state 的修改。

        如果 mapDispatchToProps 是一个函数,该函数会接收 store.state容器组件的 props 作为参数, 返回一个对象 - {'xx': wrappedFunction}。 其中 wrappedFunction 为包装 dispatch 以后的方法。 mapDispatchToProps 的返回值会传递给 展示组件展示组件 通过 props.xx 使用 wrappedFunction 来触发 store.state 的修改。

      • mergeProps? : Function

        mergePropsconnect 方法执行时传入的 第三个参数,是一个 函数, 用于 构建传递给展示组件的 props

        传递给 展示组件props 有三种类型: stateProps - mapStateToProps 返回的结果dispatchProps - mapDispatchToProps 返回的结果ownProps - 容器组件的 props

        mergeProps 决定了如何将 stateProps、dispatchProps、ownProps 合并 为传递给 展示组件props

        如果 未指定 mergeProps,使用 {...ownProps, ...stateProps, ...dispatchProps} 构建 props

        如果 指定 mergeProps,使用 用户自定义方式构建 props, 传入的参数依次为 stateProps, dispatchProps, ownProps

      • options? : Object

        connect 方法执行时传入的 第四个参数,是一个 配置项对象, 不过一般我们不需要指定,具体配置项详见: connect

    • 返回值

      返回一个 包装方法 - wrapWithConnect,用以将 展示组件 转化为 容器组件

    • 用法

      常见用法如下:

      const mapStateToProps = function () {...}
      const mapDispatchToPros = function () {...}
      // 展示组件
      const TodoList = function (props) {...}
      // 容器组件
      const TodoList = connect(mapStateToProps, mapDispatchToPros)(TodoList)
      

      具体介绍详见: Connect

  • Provider

    • 功能

      store 提供给嵌套在 中的所有容器组件(或者使用 hook 的组件) 使用。

      Provider 会利用 React.createContext 构建一个 Context 对象。 store 会保存到 Context 对象的 value 中。 被 Provider 包裹的所有容器组件(或者使用 hook 的组件) 都可以从 Context 中读取 store 对象。

    • 用法

      常见用法如下:

      var a = ReactDOM.render(
          <ReactRedux.Provider store={store}>
              <App />
          </ReactRedux.Provider>,
          document.getElementById('app')
      )
      
      

      具体介绍详见: Provider

  • useSelector

    • 功能

      函数式组件store.state 中获取指定的 state

      使用 useSelector 时, 会给 函数式组件 定义一个 更新机制,将这个 更新机制包装为一个 listener 并通过 store.substribe 添加到 store 对象的 listeners 列表中。

      如果通过 dispatch 方式修改 store.state 时, 遍历 listeners 列表执行 listener,触发 函数式组件的更新机制

      更新函数式组件 时, 会再次从 store.state 中获取指定的 state 并和上次的 state 做比较。如果不同, 函数式组件触发更新, 重新渲染。

    • 参数

      使用 useSelector 时,需要传入的参数如下:

      • selector : Function

        selector 是使用 useSelector 时传入的 第一个参数是一个函数且必传, 用于从 store.state 中获取 指定的 state

        selector 执行时只需要传入 store.state, 然后返回用户 指定的 state

      • equalityFn? : Function

        equalityFn 是使用 useSelector 时传入的 第二个参数是一个函数且非必传, 用于 判断函数式组件是否需要更新

        store.state 发生变化时, 会通知 使用 useSelector 的函数式组件 去触发 更新机制。 此时,会先使用 seletor 获取从更新以后的 store.state 中获取 指定的 state,然后和 上一次的 state 做比较。如果 新旧state 不一样,函数式组件才会触发更新, 重新渲染。

        比较新旧 state 会使用 equalityFn 指定的方法。如果未指定 equalityFn, 默认使用 "===" 的方式比较

    • 返回值

      指定的 state, 即 selector 的返回值

    • 用法

      常见用法如下:

      function Component (props) {
          const todo = useSelector(state = state.todos);
          return ...
      }
      

      具体介绍详见: hooks

  • useDispatch

    • 功能

      store 中获取 dispatch函数式组件 使用。

    • 返回值 : store.dispatch

    • 用法

      常见用法如下:

      function Component (props) {
          const dispatch = useDispatch();
          return ...
      }
      

      具体介绍详见: hooks

  • useStore

    • 功能

      获取 store函数式组件 使用。

      在函数式组件通过 useStore 来使用 store.state, 如果 store.state 发生变化,不会触发函数式组件更新

    • 返回值 : store

    • 用法

      常见用法如下:

      function Component (props) {
          const dispatch = useStore();
          return ...
      }
      

      具体介绍详见: hooks

总结

使用 react-redux关注的知识点 如下:

  1. 使用 connect 时未指定 mapStateToProps, store.state 变化时容器组件及展示组件不会触发更新。

  2. 使用 connect 时指定 mapStateToProps, 且容器组件关联的 state 发生变化,容器组件及展示组件触发更新。

  3. 使用 connnect 时指定 mapDispatchToProps,如果是对象, 对象的属性值必须是 ActionCreator。

  4. 使用 connect 时未指定 mapDispatchToProps,会将 store.dispatch 传递给展示组件。

  5. 容器组件自身的 props 会传递给 展示组件。

  6. 容器组件和使用 hooks 的组件必须包裹在 Provider 中, 否则无效。

  7. 函数式组件使用 useSelector 时, 只有关联的 state 发生变化, 函数式组件才会触发更新。

  8. 不要通过 useStore 返回的 store 来获取 state,否则 store.state 的变化不会触发 函数式组件的更新。

  9. connect 方法适用于所有组件, hooks 适用于函数式组件。

  10. 函数式组件中多次使用 useSelector,相应的 state 都发生变化时,函数式组件只渲染一次。