摘要:在本教程中,您将学习如何使用 React useReducer Hook 来处理复杂的状态逻辑。
React useReducer() Hook 简介
useReducer Hook 是 useState Hook 的替代方案,允许您管理组件的状态
useReducerHook 生成状态。- 更改状态会触发组件重新渲染。
useReducer Hook 在以下情况下很有用
- 组件具有多个密切相关的状态片段。
- 未来的状态值取决于当前状态。
要使用 useReducer Hook,请按照以下步骤操作
步骤 1. 从 react 库导入 useReducer Hook
import { useReducer } from 'react';Code language: JavaScript (javascript)步骤 2. 定义一个 reducer() 函数,该函数接受两个参数 state 和 action
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 的工作原理。

React useReducer() 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() 函数,该函数具有两个参数 state 和 action
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是一个对象,它包含所有状态变量,包括count和step。dispatch在功能上等效于setCount和setStep函数。
在右侧
reducer是一个函数,它决定如何更新state对象中的count和step属性。{count: 0, step: 1}是初始状态。
下图说明了 useState 和 useReducer Hook 的等效性

如果您想更新状态,您可以调用 dispatch() 函数,它是从调用 useReducer() 函数返回的函数。
当您调用 dispatch() 函数时,React 将找到 reducer 函数并执行它。
reducer 函数应该返回一个新的状态。如果它没有返回任何内容,状态将是 undefined。
reducer 函数不能直接修改状态。相反,它应该返回一个具有更新属性的新状态副本。
此外,reducer() 函数不能包含 async/await、API 请求、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)总结
useReducerHook 是useStateHook 的替代方案。- 当组件具有多个密切相关的状态片段,并且未来的状态取决于当前状态时,使用
useReducerHook。 - 使用
Immer包可以更方便地处理状态。