React useReducer Hook

摘要:在本教程中,您将学习如何使用 React useReducer Hook 来处理复杂的状态逻辑。

React useReducer() Hook 简介

useReducer Hook 是 useState Hook 的替代方案,允许您管理组件的状态

  • useReducer Hook 生成状态。
  • 更改状态会触发组件重新渲染。

useReducer Hook 在以下情况下很有用

  • 组件具有多个密切相关的状态片段。
  • 未来的状态值取决于当前状态。

要使用 useReducer Hook,请按照以下步骤操作

步骤 1. 从 react 库导入 useReducer Hook

import { useReducer } from 'react';Code language: JavaScript (javascript)

步骤 2. 定义一个 reducer() 函数,该函数接受两个参数 stateaction

function reducer(state, action) {
  // ...
}Code language: JavaScript (javascript)

步骤 3. 在组件中使用 useReducer Hook

function MyComponent() {
   const [state, dispatch] = useReducer(reducer, initialArg, init?);
   // ...
}Code language: JavaScript (javascript)

以下是 useReducer Hook 的语法

  • reducer 是一个函数,它决定如何更新状态。
  • initialArg 是初始状态。
  • init 是一个可选参数,它指定一个返回初始状态的函数。如果您省略它,初始状态将设置为 initialArg。否则,初始状态将设置为调用 init(initialArg) 的结果。

useReducer() 函数返回一个包含两个值的数组

  • state 是当前状态。
  • dispatch 函数允许您将状态更新为新的状态并触发重新渲染。

以下显示了 useReducer Hook 的工作原理。

useReducer

React useReducer() Hook 示例

以下说明了一个计数器应用程序

如果您单击增加按钮,它将使计数增加一。如果您单击减少按钮,它将使计数减少一。

如果您更改步长并单击增加或减少按钮,它将使计数根据步长增加或减少。

下载使用 useState Hook 的计数器应用程序,

以下是 App 组件的源代码

import { useState } from 'react';
import './App.css';

const App = () => {
  const [count, setCount] = useState(0);
  const [step, setStep] = useState(1);

  const increment = () => setCount(count + step);
  const decrement = () => setCount(count - step);
  const handleChange = (e) => setStep(parseInt(e.target.value));

  return (
    <>
      <header>
        <h1>Counter</h1>
      </header>
      <main>
        <section className="counter">
          <p className="leading">{count}</p>
          <div className="actions">
            <button
              type="button"
              className="btn btn-circle"
              onClick={decrement}
            >
              -
            </button>
            <button
              type="button"
              className="btn btn-circle"
              onClick={increment}
            >
              +
            </button>
          </div>
        </section>

        <section className="counter-step">
          <label htmlFor="step">Step</label>
          <input
            id="step"
            type="range"
            min="1"
            max="10"
            value={step}
            onChange={handleChange}
          />
          <label>{step}</label>
        </section>
      </main>
    </>
  );
};

export default App;Code language: JavaScript (javascript)

App 组件有两个相关的状态片段

  • count
  • step

此外,下一个状态取决于上一个状态

const increment = () => setCount(count + step);
const decrement = () => setCount(count - step);Code language: JavaScript (javascript)

我们将用 useReducer Hook 替换 useState Hook。

步骤 1. 定义一个 reducer() 函数,该函数具有两个参数 stateaction

const reducer = (state, action) => {
  // ...
};Code language: JavaScript (javascript)

步骤 2. 删除 useState Hook,并改用 useReducer Hook

// const [count, setCount] = useState(0);
// const [step, setStep] = useState(1);

const [state, dispatch] = useReducer(reducer, { count: 0, step: 1 });Code language: JavaScript (javascript)

在左侧

  • state 是一个对象,它包含所有状态变量,包括 countstep
  • dispatch 在功能上等效于 setCountsetStep 函数。

在右侧

  • reducer 是一个函数,它决定如何更新 state 对象中的 countstep 属性。
  • {count: 0, step: 1} 是初始状态。

下图说明了 useStateuseReducer Hook 的等效性

React useReducer

如果您想更新状态,您可以调用 dispatch() 函数,它是从调用 useReducer() 函数返回的函数。

当您调用 dispatch() 函数时,React 将找到 reducer 函数并执行它。

reducer 函数应该返回一个新的状态。如果它没有返回任何内容,状态将是 undefined

reducer 函数不能直接修改状态。相反,它应该返回一个具有更新属性的新状态副本。

此外,reducer() 函数不能包含 async/awaitAPI 请求Promise 或更改外部变量。换句话说,它必须是一个纯函数。

当您调用 dispatch() 函数时,您需要传递一个对象来告诉 reducer() 函数如何更新状态。

按照惯例,您需要传递一个 action 对象,该对象具有两个属性

{type: 'ACTION', payload: value }Code language: JavaScript (javascript)
  • type 是一个字符串,它告诉 reducer 更新哪个状态。
  • payload 是传递给 reducer 的值。

action 对象中,type 属性是必需的,而 payload 属性是可选的。

例如,如果用户单击增加按钮,您需要像下面这样调用 dispatch() 函数

dispatch({ type: 'INCREMENT' })Code language: JavaScript (javascript)

reducer() 函数中,您可以检查 action 对象并相应地更新状态

if(action.type === 'INCREMENT') {
    return {
        ...state,
        count: state.count + state.step
    };
}Code language: JavaScript (javascript)

类似地,如果用户单击减少按钮,您需要使用不同的 action 类型调用 dispatch() 函数

dispatch({ type: 'DECREMENT' })Code language: JavaScript (javascript)

并在 reducer() 函数内部检查 action 对象以减少状态的 count 属性

if(action.type === 'DECREMENT') {
    return {
        ...state,
        count: state.count - state.step
    };
}Code language: JavaScript (javascript)

如果用户更改状态,您可以像下面这样调用 dispatch() 函数

dispatch({ type: 'CHANGE_STEP', payload: newStep });Code language: JavaScript (javascript)

reducer() 函数中,您可以相应地更新状态的 step 属性

if(action.type === 'CHANGE_STEP') {
    return {
        ...state,
        step: action.payload
    };
}Code language: JavaScript (javascript)

由于 reducer() 函数处理许多 action 类型,我们可以使用 switch…case 语句

const reducer = (state, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return {
        ...state,
        count: state.count + state.step,
      };
    case 'DECREMENT':
      return {
        ...state,
        count: state.count - state.step,
      };
    case 'CHANGE_STEP':
      return {
        ...state,
        count: action.payload,
      };
    default:
      throw new Error(`action type ${action.type} is unexpected.`);
  }
};Code language: JavaScript (javascript)

请注意,如果 action 类型没有确定,您可以抛出错误或直接忽略它。

以下是使用 useReducer Hook 而不是 useState Hook 的完整 App 组件

import { useReducer, useState } from 'react';
import './App.css';

const reducer = (state, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return {
        ...state,
        count: state.count + state.step,
      };
    case 'DECREMENT':
      return {
        ...state,
        count: state.count - state.step,
      };
    case 'CHANGE_STEP':
      return {
        ...state,
        step: action.payload,
      };
    default:
      throw new Error(`action type ${action.type} is unexpected.`);
  }
};

const App = () => {
  // const [count, setCount] = useState(0);
  // const [step, setStep] = useState(1);

  const [state, dispatch] = useReducer(reducer, { count: 0, step: 1 });

  // const increment = () => setState(count + step);
  // const decrement = () => setCount(count - step);
  // const handleChange = (e) => setStep(parseInt(e.target.value));

  const increment = () => dispatch({ type: 'INCREMENT' });
  const decrement = () => dispatch({ type: 'DECREMENT' });
  const handleChange = (e) =>
    dispatch({
      type: 'CHANGE_STEP',
      payload: parseInt(e.target.value),
    });

  return (
    <>
      <header>
        <h1>Counter</h1>
      </header>
      <main>
        <section className="counter">
          <p className="leading">{state.count}</p>
          <div className="actions">
            <button
              type="button"
              className="btn btn-circle"
              onClick={decrement}
            >
              -
            </button>
            <button
              type="button"
              className="btn btn-circle"
              onClick={increment}
            >
              +
            </button>
          </div>
        </section>

        <section className="counter-step">
          <label htmlFor="step">Step</label>
          <input
            id="step"
            type="range"
            min="1"
            max="10"
            value={state.step}
            onChange={handleChange}
          />
          <label>{state.step}</label>
        </section>
      </main>
    </>
  );
};

export default App;Code language: JavaScript (javascript)

下载项目源代码

下载项目源代码

使用 Immer 包来处理不可变状态

在 reducer 函数中,我们不能直接修改状态,而应该返回一个修改后的状态的副本。这很不方便。

要更方便地处理状态,您可以使用 Immer 包。因此,与其返回一个修改后的状态的副本,像这样

return {
  ...state,
  count: state.count + state.step,
};
Code language: JavaScript (javascript)

您可以像下面这样直接修改状态

state.count = state.count + state.step;Code language: JavaScript (javascript)

您可以按照以下步骤使用 Immer 包

步骤 1. 安装 Immer 包

npm install immerCode language: JavaScript (javascript)

步骤 2. 将 produce 函数导入组件

import { produce } from 'immer';Code language: JavaScript (javascript)

步骤 3. 第三,用 produce 函数包装 reducer 函数

const [state, dispatch] = useReducer(produce(reducer), { count: 0, step: 1 });Code language: JavaScript (javascript)

步骤 4. 简化直接修改状态的 reducer() 函数

const reducer = (state, action) => {
  switch (action.type) {
    case 'INCREMENT':
      state.count = state.count + state.step;
      break;
    case 'DECREMENT':
      state.count = state.count - state.step;
      break;
    case 'CHANGE_STEP':
      state.step = action.payload;
      break;
    default:
      throw new Error(`action type ${action.type} is unexpected.`);
  }
};Code language: JavaScript (javascript)

下载项目源代码

总结

  • useReducer Hook 是 useState Hook 的替代方案。
  • 当组件具有多个密切相关的状态片段,并且未来的状态取决于当前状态时,使用 useReducer Hook。
  • 使用 Immer 包可以更方便地处理状态。
本教程是否有帮助?