useReducer 相当于 Vuex 中的 store 和 mutations 的结合。
useReducer 更适用于处理复杂的状态逻辑,尤其是当状态之间存在关联,或者需要多个状态同时更新时。可以让我们的代码具有更好的可读性、可维护性、可预测性。
const [state, dispatch] = useReducer(reducer, initialArg, init?)
在组件的顶层作用域调用 useReducer 以创建一个用于管理状态的 reducer。
reducer:用于更新 state 的纯函数。参数为 state 和 action,返回值是更新后的 state。state 与 action 可以是任意合法值。(往函数体里面添加计算并返回新的 state 的逻辑)initialArg:用于初始化 state 的任意值。初始值的计算逻辑取决于接下来的 init 参数。
dispatch 函数。用于更新 state 并触发组件的重新渲染。
严格模式下 React 会 调用两次 reducer 和初始化函数
dispatch(action)
action: 用户执行的操作。可以是任意类型的值。通常来说 action 是一个对象,其中 type 属性标识类型,其它属性携带额外信息。
import { useReducer } from 'react';
function reducer(state, action) {
switch (action.type) {
case 'incremented_age': {
return {
name: state.name,
age: state.age + 1
};
}
case 'changed_name': {
return {
name: action.nextName,
age: state.age
};
}
}
throw Error('Unknown action: ' + action.type);
}
function MyComponent() {
const [state, dispatch] = useReducer(reducer, { age: 42 });
// ...
const [state, dispatch] = useReducer(reducer, { age: 42 });
function handleClick() {
dispatch({ type: 'incremented_age' });
// ...
state 的类型也是任意的,不过一般会使用对象或数组。
state 是只读的。即使是对象或数组也不要尝试修改它:
//错误
function reducer(state, action) {
switch (action.type) {
case 'incremented_age': {
// ???? 不要像下面这样修改一个对象类型的 state:
state.age = state.age + 1;
return state;
}
// 正确
function reducer(state, action) {
switch (action.type) {
case 'incremented_age': {
// ✅ 正确的做法是返回新的对象
return {
...state,
age: state.age + 1
};
}
当需要处理复杂的状态逻辑,或者需要进行多个相关状态的更新,并且这些状态需要一起进行更改时,useReducer是一个适合的选择。
表单处理
购物车功能
应用主题
数据筛选和排序
步骤导航
数组 不能使用修改方法 来更新。
function tasksReducer(tasks, action) {
switch (action.type) {
case 'added': {
return [...tasks, {
id: action.id,
text: action.text,
done: false
}];
}
case 'deleted': {
return tasks.filter(t => t.id !== action.id);
}
default: {
throw Error('Unknown action: ' + action.type);
}
}
}
使用 Immer 编写简洁的更新逻辑
避免重新创建初始值
function createInitialState(username) {
// ...
}
function TodoList({ username }) {
const [state, dispatch] = useReducer(reducer, createInitialState(username));
虽然 createInitialState(username) 的返回值只用于初次渲染,但是在每一次渲染的时候都会被调用。如果它创建了比较大的数组或者执行了昂贵的计算就会浪费性能。
你可以通过给 useReducer 的第三个参数传入 初始化函数 来解决这个问题:
function createInitialState(username) {
// ...
}
function TodoList({ username }) {
const [state, dispatch] = useReducer(reducer, username, createInitialState);
// ...
疑难看下面的示例
import { useCallback, useEffect, useRef, useReducer } from 'react';
const reducer = (state, action) => {
const { type } = action;
if (type === 'add') {
return {
...state,
value: state.value + 1,
};
} else {
return {
...state,
value: state.value - 1,
};
}
};
export default function useTableListHooks() {
const [store2, dispatch] = useReducer(reducer, { value: 0, name: 'zs' });
const clickFn = () => {
dispatch({ type: 'aaa' });
console.log(store2);
};
return {
store2,
clickFn,
}
}
const {
store2,
clickFn,
} = useHooks();
<div onClick={clickFn}>
{store2.name} - {store2.value}
</div>