摘要:在本教程中,您将学习如何使用 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()
函数,该函数接受两个参数 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 immer
Code 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
包可以更方便地处理状态。