用过vue的同学应该会有点体会(好像大家尝试解释一件事物时都习惯用以往的经验类比),这个东西有点像computed计算属性,就是将全局维护的响应式数据进行计算操作。但是这个userReducer远比computed灵活,它是一个纯函数,你可以写在组件内也可以提出去当成公用的方法。
先说一下它的实现原理(因为我本身最开始使用时,不明就里,只是记住了用法):
import { useState } from 'react';
// reducer: 我们自己编写的reducer纯函数
// initialState: 初始化状态值
export function useReducer(reducer, initialState) {
const [state, setState] = useState(initialState); // 我们传入的初始值 先注册成响应式数据
// 抛出给我们调用的dispatch函数(就是调用userReducer抛出的第二个返回值) ,
function dispatch(action) {
// 当我们调用时,实际上就是调用了我们传入的reducer纯函数
const nextState = reducer(state, action);
// 然后将纯函数得到的值存起来,最后一并抛出
setState(nextState);
}
return [state, dispatch];
}
// 其实dispatch更准确的实现是这样的,也遵循react的性能优化逻辑,本质上是异步执行:
function dispatch(action) {
setState((s) => reducer(s, action));
}
// 这是因为被派发的 actions 在下一次渲染之前都是处于排队状态的,这和 状态更新函数 类似。
看完你会发现这个hooks它好像没做啥啊,就是中转又封装了一下调用和设置响应式数据,我自己又得手写reducer纯函数,又得调用dispatch传参去触发。当然不是,它的意义就在于将大量的重复性的或者具有封装性的操作归拢在了一个地方且依然给你响应式数据和符合react的异步更新的逻辑。
并不是所有人都喜欢用 reducer,这是个人偏好问题。你可以在 useState 和 useReducer 之间切换,它们能做的事情是一样的。
用法
经典举例:一个TODO LIST,有增删改查基本操作,都是对list进行操作,当然常规写法就是你写四个方法,在里面进行list的增删改查操作然后setState,这当然可以。但是reducer可以把这四种操作归拢起来写成一个函数,具有封装性也显得代码整齐且好维护:
// reducer.js 可以集中维护项目里面的所有reducer
function todoReducer(list, action) {
switch (action.type) {
case 'added': {
return [
...list,
{
id: action.id,
text: action.text,
done: false,
},
];
}
case 'changed': {
return list.map((t) => {
if (t.id === action.listItem.id) {
return action.listItem;
} else {
return t;
}
});
}
case 'deleted': {
return list.filter((t) => t.id !== action.id);
}
default: {
throw Error('未知 action: ' + action.type);
}
}
}
// todo.jsx 组件页面
import { useReducer } from 'react';
import todoReducer from './reducer.js';
export default function TodoApp() {
let nextId = 3;
const initialList = [
{id: 0, text: '吃', done: true},
{id: 1, text: '看', done: false},
{id: 2, text: '喝', done: false}
];
const [list, dispatch] = useReducer(todoReducer, initialList);
// 用户点击 添加 按钮 ;PS:入参text就是用户输入的东西
function handleAdd(text) {
dispatch({
type: 'added',
id: nextId++,
text: text,
});
}
// 删除 入参是删除项标识
function handleDel(listId) {
dispatch({
type: 'deleted',
id: listId
});
}
// 编辑 入参是修改项
function handleChange(listItem) {
dispatch({
type: 'changed',
listItem: listItem
});
}
return (
<>
<h1 onClick={handleAddTask}>添加</h1>
... ...
</>
)
}
像这种 将页面调用和逻辑分开的叫关注点分离(装一波)
reducer是一个纯函数,即入参是啥 出参是啥:
1、Reducers 可能需要你写更多的代码,但是这有利于代码的调试和测试。
2、Reducers 必须是纯净的。
3、每个 action 都描述了一个单一的用户交互。
4、使用 Immer 来帮助你在 reducer 里直接修改状态(这大家可以研究一下,因为reducer不允许直接修改传入的状态,这个Immer是内部将state拷贝了一份供你修改,所以不影响原状态)
改变总是很难的嘛!
但是你回头看看呢 已经走出了二里地!