拥抱React-Hooks (五) - useReducer

Redux 基础


我们都知道,像 React 框架,本地 state 是交给用户自己打理的,它们分散在组件树中,并随着数据的流动而相互影响。随着应用的复杂性提升,state 也会越来越复杂。很容易就陷入 state难管理,逻辑难追踪,应用难维护的境地。

Redux 的出现就是为了解决以上的问题。目标是让 state 可维护,可预测,可持续。
Redux 原理很简单,它就是 Javascript + 设计模式(思想)。其设计模式也非常简单,可总结为:

三原则

  • 单一数据源 store 用来存储 state
  • state 只读,只能通过 action修改
  • 使用纯函数执行 state 修改,需要编写 reducers

对应三要素:

  • Store 数据集中存储的容器。
  • Action 含有type属性的一个对象,修改数据的唯一途径,它会运送数据到 Store
  • Reducer 一个纯函数,接受当前 StateAction 作为参数,返回一个新的 State

Store 三方法:

  • store.getState(): 获取当前 state
  • store.dispatch(action): View 发出 Action 的唯一方法。
  • store.subscribe(func): 订阅方法,当 state 改变,会触发 func 重新执行。

React中应用Redux的大致流程图:

React 中使用 Redux 的代码示例:

import React from "react";
import ReactDOM from "react-dom";
import { createStore } from "redux";

// 创建reducer纯函数计算state
const reducer = (state = 0, action) => {
  switch (action.type) {
    case "INCREMENT":
      return state + 1;
    case "DECREMENT":
      return state - 1;
    default:
      return state;
  }
};
// 使用 createStore 方法创建 store,参数为 reducer
// 多个reducer的情况下 应该有一个reducer工厂方法
const store = createStore(reducer);

const rootElement = document.getElementById("root");
const render = () => {
  // 使用 getState 方法 获取state
  const count = store.getState();
  // 使用 dispatch 方法 发送 Action 触发state修改
  const INCREMENT = () => {
    store.dispatch({ type: "INCREMENT" });
  };
  const DECREMENT = () => {
    store.dispatch({ type: "INCREMENT" });
  };
  ReactDOM.render(
    <React.StrictMode>
      <div>
        <h1>{count}</h1>
        <button onClick={INCREMENT}>+</button>
        <button onClick={DECREMENT}>-</button>
    </div>
    </React.StrictMode>,
    rootElement
  );
};
render();
// 通过 subscribe 方法 关联 store 和 App 组件
// 当 state 改变 触发组件重新渲染
store.subscribe(render);

UseReducer Hook


useReducer hook 用于 React 函数组件中管理复杂的 state 。它把一个reducer方法,和初始state作为输入,包含当前 state,和一个 dispatch 方法的 解构数组 作为输出。

API( 对照 useState ):

// useReducer hook API
const [current, dispatch] = useReducer(reducer, initState);

// useState hook API
const [current, setFunc] = useReducer(initState);
  • reducer 用于改变 statereducer 函数,同 Redux
  • initState 初始state
  • current 当前state
  • dispatch 负责传递一个 actionreducer 函数,以此改变当前 state

useState 相比,useReducer 多一个reducer 函数。reducer 函数通过 dispatch 传递的 action执行不同的state修改操作。

redux相比,用户无需关心 store 对象。另外 state 改变,组件会自动触发重新渲染。

来看一个使用 useReducer 的简单例子:

const initialState = {count: 0};

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    default:
      throw new Error();
  }
}

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

上述示例中的 state 很简单,其实使用 useState 就可以。那么,我们什么时候该使用 useReducer 呢?


useReducer 适用场景


我们先来看看和 state 管理相关的三大 Hooks 定位:

  • useState: 简单 State
  • useReducer: 复杂 State
  • useContext: 全局 State

适用于useReducer 的复杂 state 的场景主要有:

  • state 逻辑较复杂且包含多个子值(大的对象,数组)。
  • state 更新依赖于之前的 state
  • state 组件树深层更新。使用useReducer可以向子组件传递 dispatch 而不是回调函数,这样可以优化性能。

相对于 useStateuseReducer只是略复杂。所以当 state 有一定复杂度,便可以大胆使用 useReducer。我们更多的不是纠结要不要使用 useReducer,而是怎么用好 useReducer

比如上面的代码示例中,reducer 函数可以优化一下,使其可持续发展:

...
// 用好`useReducer`的关键 - reducer 函数
function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {...state, { count: state.count + 1} }; // 看这里
    case 'decrement':
      return {...state, { count: state.count - 1} }; // 看这里
    default:
      throw new Error();
  }
}
...

这样改写的原因是,随着组件复杂度提升,state 对象会扩展其他属性,而不仅有 count


参考资料

Redux 入门教程(一)
Redux 中文网
What is a Reducer in JavaScript/React/Redux?
How to useReducer in React