受够了useState的逻辑分散?来,试试用reducer聚合逻辑
useState的缺点
经常写react 的同学都知道,useState
是 React 中的一个 Hook,可以在函数组件中管理组件的状态,并在状态更新时重新渲染组件。
这东西虽然简单好用,但有一个致命缺点:当组件有非常多的状态更新逻辑时,事件处理会非常分散,维护起来很头疼!
比如,一个简单的记事本功能
我需要通过三个不同的事件处理程序来实现任务的添加、删除和修改:
const [tasks, setTasks] = useState([
{ id: 1, title: '去贝加尔湖旅行', completed: false },
{ id: 2, title: '去烟台海边度假', completed: false },
{ id: 3, title: '再去一次厦门看海', completed: false },
]);
// 添加
const addTask = (taskTitle) => {
const newId = tasks.length + 1;
const newTask = { id: newId, title: taskTitle, completed: false };
setTasks([...tasks, newTask]);
};
// 删除
const deleteTask = (taskId) => {
setTasks(tasks.filter(task => task.id !== taskId));
};
// 编辑
const toggleTaskCompletion = (taskId) => {
setTasks(tasks.map(task =>
task.id === taskId ? { ...task, completed: !task.completed } : task
));
};
上面的代码中,每个事件处理程序都通过 setTasks
来更新状态。随着这个组件功能的,其状态逻辑也会越来越多。为了降低这种复杂度,并让所有逻辑都可以存放在一个易于理解的地方,我们可以将这些状态逻辑移到组件之外的一个称为 reducer 的函数中。
使用 reducer 整合状态逻辑
在学习reducer之前,我们先看看使用reducer整合后的代码
import React, { useReducer, useState } from 'react';
// 定义 reducer 函数
const taskReducer = (state, action) => {
switch (action.type) {
case 'ADD_TASK':
const newId = state.length + 1;
return [...state, { id: newId, title: action.payload, completed: false }];
case 'DELETE_TASK':
return state.filter(task => task.id !== action.payload);
case 'TOGGLE_TASK':
return state.map(task =>
task.id === action.payload ? { ...task, completed: !task.completed } : task
);
default:
return state;
}
};
function TaskList() {
const [tasks, dispatch] = useReducer(taskReducer, [
{ id: 1, title: '去贝加尔湖旅行', completed: false },
{ id: 2, title: '去烟台海边度假', completed: false },
{ id: 3, title: '再去一次厦门看海', completed: false },
]);
const [newTaskTitle, setNewTaskTitle] = useState('');
const handleAddTask = () => {
if (newTaskTitle.trim()) {
dispatch({ type: 'ADD_TASK', payload: newTaskTitle });
setNewTaskTitle('');
}
};
return (
<div>
<h2>快乐就是哈哈哈的记事本</h2>
<div>
<input
type="text"
placeholder="添加新任务"
value={newTaskTitle}
onChange={(e) => setNewTaskTitle(e.target.value)}
/>
<button onClick={handleAddTask} style={{ marginLeft: '10px' }}>添加</button>
</div>
<div>
{tasks.map(task => (
<div key={task.id} style={{ marginTop: '10px' }}>
<input
type="checkbox"
checked={task.completed}
onChange={() => dispatch({ type: 'TOGGLE_TASK', payload: task.id })}
/>
{task.title}
<button onClick={() => { /* Add edit functionality here */ }} style={{ marginLeft: '10px' }}>编辑</button>
<button onClick={() => dispatch({ type: 'DELETE_TASK', payload: task.id })} style={{ marginLeft: '10px' }}>删除</button>
</div>
))}
</div>
</div>
);
}
export default TaskList;
能够看出,所有的逻辑被整合到taskReducer这个函数中了,我们的逻辑聚合度很高,非常好维护!
useReducer的基本语法
const [state, dispatch] = useReducer(reducer, initialArg, init?)
在组件的顶层作用域调用 useReducer 以创建一个用于管理状态的 reducer。
import { useReducer } from 'react';
function reducer(state, action) {
// ...
}
function MyComponent() {
const [state, dispatch] = useReducer(reducer, { age: 42 });
// ...
参数
reducer
:用于更新 state 的纯函数。参数为 state 和 action,返回值是更新后的 state。state 与 action 可以是任意合法值。initialArg
:用于初始化 state 的任意值。初始值的计算逻辑取决于接下来的init
参数。- 可选参数
init
:用于计算初始值的函数。如果存在,使用init(initialArg)
的执行结果作为初始值,否则使用initialArg
。
返回值
useReducer
返回一个由两个值组成的数组:
- 当前的 state。初次渲染时,它是
init(initialArg)
或initialArg
(如果没有init
函数)。 - dispatch函数。用于更新 state 并触发组件的重新渲染。
dispatch 函数
dispatch 函数可以用来更新 state的值 并触发组件的重新渲染,它的用法其实和vue的store,react的状态管理库非常相似!
dispacth可以有很多,通过dispacth可以发送数据给reducer函数,函数内部,我们通过action可以拿到所有dispatch发送的数据,然后进行逻辑判断,更改state的值。
通常来说 action 是一个对象,其中 type 属性标识类型,其它属性携带额外信息。
代码解读
熟悉了它的语法后,我们的整合逻辑就非常好理解了。我们简化下逻辑:
import React, { useReducer, useState } from 'react';
// 定义 reducer 函数
const taskReducer = (state, action) => {
switch (action.type) {
// 根据不同逻辑,返回一个新的state的值
default:
return state;
}
};
function TaskList() {
const [tasks, dispatch] = useReducer(taskReducer, [
{ id: 1, title: '去贝加尔湖旅行', completed: false },
{ id: 2, title: '去烟台海边度假', completed: false },
{ id: 3, title: '再去一次厦门看海', completed: false },
]);
// 通过dispatch发送数据给taskReducer
return (
<div>
<h2>快乐就是哈哈哈的记事本</h2>
<div key={task.id} style={{ marginTop: '10px' }}>
<input
type="checkbox"
checked={task.completed}
onChange={() => dispatch({ type: 'TOGGLE_TASK', payload: task.id })}
/>
<button onClick={() => dispatch({ type: 'DELETE_TASK', payload: task.id })} style={{ marginLeft: '10px' }}>删除</button>
</div>
</div>
);
}
export default TaskList;
useReducer的性能优化
我们先看看下面的代码
function createInitialState(username) {
// ...
// 生成初始值的一些逻辑
}
function TodoList({ username }) {
const [state, dispatch] = useReducer(reducer, createInitialState(username));
// ...
createInitialState方法用于生成初始值,但是在每一次渲染的时候都会被调用,如果创建了比较大的数组或计算是比较浪费性能的!
我们可以通过给 useReducer 的第三个参数传入 初始化函数 来解决这个问题:
function createInitialState(username) {
// ...
}
function TodoList({ username }) {
const [state, dispatch] = useReducer(reducer, username, createInitialState);
// ...
需要注意的是你传入的参数是 createInitialState 这个 函数自身,而不是执行 createInitialState() 后的返回值
如果createInitialState可以直接计算出初始值,不需要默认的username,上面的代码可以进一步优化
function createInitialState() {
// ...
}
function TodoList() {
const [state, dispatch] = useReducer(reducer, null, createInitialState);
// ...
来源:juejin.cn/post/7399496845277151242