与 React 整合

Redux 与 React 的整合主要通过react-redux这个绑定库。并且主要的实现目的就是将 React 组件与 Redux 关联起来。但是需要注意的是,在 React 中使用 Redux,React-Redux 这个绑定库并不是必需的,而且使用这个库需要掌握额外的 API,并要遵循其组件拆分规范。

Tip

根据 Redux 作者的建议,只有遇到 React 解决不了的问题,才会需要 Redux。在大多数情况下,React 已经足够,所以对于在项目中使用 Redux,要根据项目需要仔细甄别。按照一般经验来说,在以下场景内会用到 Redux。

  • 用户的使用方式复杂。
  • 不同的身份的用户有不同的使用方式,例如普通用户与管理员。
  • 多个用户之间需要协作。
  • 与服务器有大量的交互,或者使用了 WebSocket。
  • UI 视图需要从多个来源获取数据。

简而言之,在组件的状态需要共享,并且需要操作全局状态甚至更改其他组件的状态的情况下,可以考虑使用 Redux。但是需要牢记的是,Redux 只是一种架构解决方案,不是唯一的。

React-Redux 中将 React 组件分为两大类:容器组件和 UI 组件。其中 UI 组件只负责 UI 的呈现,不带有任何业务逻辑,不使用this.state保存本地 State,所有数据都由props提供,并且不使用任何 Redux API。所以 UI 组件与之前提到的纯函数一样,给定相同的参数,产生相同的 UI 显示。而容器组件则完全相反,只处理数据和业务逻辑,不负责 UI 呈现,并且带有内部状态,也是用 Redux API。

从原理上说,就是在 React 中创建容器组件,通过store.subscribe()从 State 树中读取数据,并通过props提供给 UI 组件。但是 React-Redux 规定,所有的 UI 组件都由用户提供,容器组件都由 React-Redux 自动生成。所以 React-Redux 提供了connect()方法,用于从 UI 组件中生成容器组件。

connect()方法接受两个参数,两个参数均为函数,其中第一个参数用于将 State 映射到 Props,第二个参数用于将 Dispatch 映射到 Props。即第一个参数负责输入逻辑,第二个参数负责输出逻辑。

connect()的具体用法可参考以下示例。

const mapStateToProps = state => {
  return {
    contents: state.contents.filter(c => t.onShow)
  };
};

// 其中ownProps代表容器自身的props
// bindActionCreator可以将Action Creator直接放入组件供生成Action使用
const mapDispatchToProps = (dispatch, ownProps) => {
  return {
    onClick: () => {
      dispatch(addContent(ownProps.newContent));
    },
    actions: bindActionCreators({ addContent }, dispatch)
  };
};

// Contents为UI组件
const OnShowContents = connect(mapStateToProps, mapDispatchToProps)(Contents);

注意上例中mapDispatchToProps()中返回的对象,定义了 UI 组件的参数如何发出 Action。

React-Redux 中还提供了了一个 Provider 组件,用于包裹组件树的根,使整个组件树都能拿到 State,这个原理与 React 中的 Context 一致。一般用法如下。

const store = createStore(reducers, initialState);

// Provider包裹上例中使用connect建立的容器组件
ReactDOM.render(
  <Provider store={store}>
    <OnShowContents />
  </Provider>,
  document.getElementById('app')
);

Warning

不建议直接向组件中注入全局 State,这样会导致任何一个 Action 都触发整个应用的重新渲染。最好的使用习惯是每个组件只监听它所关联的部分 State。组件可以使用以下方式注入部分关注的 State 和 dispatch

export default connect(state => {
  related: state.related;
})(SomeElement);

如果使用 Hook 语法编写 React 组件,可以在 React 组件中使用useReducer()来返回当前的 State 和配套 Dispatch 方法。useReducer()接受一个形如(state, action) => newState的函数和 State 初始状态作为参数。

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <div>
      Count: {state.count}
      <button onClick={() => dispatch({ type: 'increment' })}>Add</button>
    </div>
  );
}

React-Redux v7.1 及以后的版本还提供了若干 Hook 来方便使用 Hook 方式编写的 React 组件。

  • useSelector(selector, equalityFn),用于从 Store 中提取数据。其中 selector 是一个函数,用来选择要返回的 State,equalityFn 函数通过指定一个相等规则来判断 State 是否发生了变化。
  • useDispatch(),向组件中注入 Dispatch 方法。
  • useStore(),向组件中注入整个 Store。

对于前面使用 useReducer() 编写的示例,可以使用 React-Redux 提供的功能重新改写如下。

function Counter() {
  const count = useSelector(state => state.count);
  const dispatch = useDispatch();
  return (
    <div>
      Count: {count}
      <button onClick={() => dispatch({ type: 'increment' })}>Add</button>
    </div>
  );
}