在之前的文章中我们学习了如何使用useState
管理状态。本章我们将先学习另外一种管理状态的方式useReducer
。
与 useState
直接修改状态不同,useReducer
通过分发动作(action
)来更新状态,这使得它更适合于处理涉及多个子值或需要触发复杂更新逻辑的状态,你可以将组件的所有状态更新逻辑整合到一个外部函数中,这个函数叫作 reducer
假设我们要实现以下功能:
Current value:
0
使用useState
的版本如下:
import { useState } from 'react';
const MAX = 5;
const MIN = -5;
export default function App() {
const [state, setState] = useState({count: 0, error: ''})
const handleIncrement = () => {
const newCount = state.count + 1
const hasError = newCount > MAX
setState({
...state,
count: hasError ? state.count : newCount,
error: hasError ? 'Maximum reached' : ''
})
}
const handleDecrement = () => {
const newCount = state.count - 1;
const hasError = newCount < MIN;
setState({
...state,
count: hasError ? state.count : newCount,
error: hasError ? 'Minimum reached' : ''
})
}
return (
<main>
<div>Current value:</div>
<section>{state.count}</section>
<section>{state.error}</section>
<div>
<button onClick={handleIncrement}>increment</button>
<button onClick={handleDecrement}>decrement</button>
</div>
</main>
)
}
下面让我们使用useReducer
来重构上面的版本
第一步: 定义action
, 它是一个普通的JS对象,至少需要包含一个可以表明发生了什么事情的字段,一般用type
表示
type Action = {
type : 'increment' | 'decrement'
}
第二步: 定义reducer
, reducer 函数就是你放置状态逻辑的地方。它接受两个参数,分别为当前 state
和 action
对象,并且返回的是更新后的 state
:
type State = {
count: number;
error?: string
}
function reducer(state: State, action: Action) {
switch (action.type) {
case "increment": {
const newCount = state.count + 1
const hasError = newCount > MAX
return {
...state,
count: hasError ? state.count : newCount,
error: hasError ? 'Maximum reached' : ''
};
}
case "decrement": {
const newCount = state.count - 1;
const hasError = newCount < MIN;
return {
...state,
count: hasError ? state.count : newCount,
error: hasError ? "Minimum reached" : "",
};
}
default: {
return state;
}
}
}
第三步: 在组件中使用useRecuder
js
const [state , dispatch] = useReducer(reducer, {count: 0})
useReducer
钩子接受 2 个参数:
- 一个
reducer
函数
- 一个初始的
state
它返回如下内容:
- 一个有状态的值
- 一个
dispatch
函数(用来 “派发” 用户操作给 reducer
)
下面放出完整代码
import { useReducer } from 'react';
const MAX = 5;
const MIN = -5;
type State = {
count: number;
error?: string
}
type Action = {
type : 'increment' | 'decrement'
}
function reducer(state: State, action: Action) {
switch (action.type) {
case "increment": {
const newCount = state.count + 1
const hasError = newCount > MAX
return {
...state,
count: hasError ? state.count : newCount,
error: hasError ? 'Maximum reached' : ''
};
}
case "decrement": {
const newCount = state.count - 1;
const hasError = newCount < MIN;
return {
...state,
count: hasError ? state.count : newCount,
error: hasError ? "Minimum reached" : "",
};
}
default: {
return state;
}
}
}
export default function App() {
const [state , dispatch] = useReducer(reducer, {count: 0})
return (
<main>
<div>Current value:</div>
<section>{state.count}</section>
<section>{state.error}</section>
<div>
<button onClick={() => dispatch({ type: "increment" })}>increment</button>
<button onClick={() => dispatch({ type: "decrement" })}>decrement</button>
</div>
</main>
)
}