平面电商网站建设,iis 架设 wordpress,注册网站域名需要什么资料医疗器械,定制制作网站开发参考文章
迁移状态逻辑至 Reducer 中
对于拥有许多状态更新逻辑的组件来说#xff0c;过于分散的事件处理程序可能会令人不知所措。对于这种情况#xff0c;可以将组件的所有状态更新逻辑整合到一个外部函数中#xff0c;这个函数叫作 reducer。
使用 reducer 整合状态逻…参考文章
迁移状态逻辑至 Reducer 中
对于拥有许多状态更新逻辑的组件来说过于分散的事件处理程序可能会令人不知所措。对于这种情况可以将组件的所有状态更新逻辑整合到一个外部函数中这个函数叫作 reducer。
使用 reducer 整合状态逻辑
随着组件复杂度的增加将很难一眼看清所有的组件状态更新逻辑。例如下面的 TaskApp 组件有一个数组类型的状态 tasks并通过三个不同的事件处理程序来实现任务的添加、删除和修改
import { useState } from react;
import AddTask from ./AddTask.js;
import TaskList from ./TaskList.js;export default function TaskApp() {const [tasks, setTasks] useState(initialTasks);function handleAddTask(text) {setTasks([...tasks,{id: nextId,text: text,done: false,},]);}function handleChangeTask(task) {setTasks(tasks.map((t) {if (t.id task.id) {return task;} else {return t;}}));}function handleDeleteTask(taskId) {setTasks(tasks.filter((t) t.id ! taskId));}return (h1布拉格的行程安排/h1AddTask onAddTask{handleAddTask} /TaskListtasks{tasks}onChangeTask{handleChangeTask}onDeleteTask{handleDeleteTask}//);
}let nextId 3;
const initialTasks [{id: 0, text: 参观卡夫卡博物馆, done: true},{id: 1, text: 看木偶戏, done: false},{id: 2, text: 打卡列侬墙, done: false},
];这个组件的每个事件处理程序都通过 setTasks 来更新状态。随着这个组件的不断迭代其状态逻辑也会越来越多。为了降低这种复杂度并让所有逻辑都可以存放在一个易于理解的地方可以将这些状态逻辑移到组件之外的一个称为 reducer 的函数中。
Reducer 是处理状态的另一种方式。可以通过三个步骤将 useState 迁移到 useReducer
将设置状态的逻辑 修改 成 dispatch 的一个 action编写 一个 reducer 函数在组件中 使用 reducer。
第 1 步: 将设置状态的逻辑修改成 dispatch 的一个 action
事件处理程序目前是通过设置状态来 实现逻辑的
function handleAddTask(text) {setTasks([...tasks,{id: nextId,text: text,done: false,},]);
}function handleChangeTask(task) {setTasks(tasks.map((t) {if (t.id task.id) {return task;} else {return t;}}));
}function handleDeleteTask(taskId) {setTasks(tasks.filter((t) t.id ! taskId));
}移除所有的状态设置逻辑。只留下三个事件处理函数
handleAddTask(text) 在用户点击 “添加” 时被调用。handleChangeTask(task) 在用户切换任务或点击 “保存” 时被调用。handleDeleteTask(taskId) 在用户点击 “删除” 时被调用。
使用 reducers 管理状态与直接设置状态略有不同。它不是通过设置状态来告诉 React “要做什么”而是通过事件处理程序 dispatch 一个 “action” 来指明 “用户刚刚做了什么”。而状态更新逻辑则保存在其他地方因此不再通过事件处理器直接 “设置 task”而是 dispatch 一个 “添加/修改/删除任务” 的 action。这更加符合用户的思维。
function handleAddTask(text) {dispatch({type: added,id: nextId,text: text,});
}function handleChangeTask(task) {dispatch({type: changed,task: task,});
}传递给 dispatch 的对象叫做 “action”
function handleDeleteTask(taskId) {dispatch(// action 对象{type: deleted,id: taskId,});
}它是一个普通的 JavaScript 对象。它的结构是由你决定的但通常来说它应该至少包含可以表明 发生了什么事情 的信息。
注意action 对象可以有多种结构。
按照惯例通常会添加一个字符串类型的 type 字段来描述发生了什么并通过其它字段传递额外的信息。type 是特定于组件的在这个例子中 added 和 addded_task 都可以。选一个能描述清楚发生的事件的名字
dispatch({// 针对特定的组件type: what_happened,// 其它字段放这里
});第 2 步: 编写一个 reducer 函数
reducer 函数就是放置状态逻辑的地方。它接受两个参数分别为当前 state 和 action 对象并且返回的是更新后的 state
function yourReducer(state, action) {// 给 React 返回更新后的状态
}React 会将状态设置为从 reducer 返回的状态。
在这个例子中要将状态设置逻辑从事件处理程序移到 reducer 函数中需要
声明当前状态tasks作为第一个参数声明 action 对象作为第二个参数从 reducer 返回 下一个 状态React 会将旧的状态设置为这个最新的状态。
下面是所有迁移到 reducer 函数的状态设置逻辑
function tasksReducer(tasks, action) {if (action.type added) {return [...tasks,{id: action.id,text: action.text,done: false,},];} else if (action.type changed) {return tasks.map((t) {if (t.id action.task.id) {return action.task;} else {return t;}});} else if (action.type deleted) {return tasks.filter((t) t.id ! action.id);} else {throw Error(未知 action: action.type);}
}由于 reducer 函数接受 statetasks作为参数因此可以 在组件之外声明它。这减少了代码的缩进级别提升了代码的可读性。
注意上面的代码使用了 if/else 语句但是在 reducers 中使用 switch 语句 是一种惯例。两种方式结果是相同的但 switch 语句读起来一目了然。
以后会像这样使用
function tasksReducer(tasks, action) {switch (action.type) {case added: {return [...tasks,{id: action.id,text: action.text,done: false,},];}case changed: {return tasks.map((t) {if (t.id action.task.id) {return action.task;} else {return t;}});}case deleted: {return tasks.filter((t) t.id ! action.id);}default: {throw Error(未知 action: action.type);}}
}建议将每个 case 块包装到 { 和 } 花括号中这样在不同 case 中声明的变量就不会互相冲突。此外case 通常应该以 return 结尾。如果忘了 return代码就会 进入 到下一个 case这就会导致错误
如果还不熟悉 switch 语句使用 if/else 也是可以的。
第 3 步: 在组件中使用 reducer
最后需要将 tasksReducer 导入到组件中。记得先从 React 中导入 useReducer Hook
import { useReducer } from react;接下来就可以替换掉之前的 useState:
const [tasks, setTasks] useState(initialTasks);只需要像下面这样使用 useReducer:
const [tasks, dispatch] useReducer(tasksReducer, initialTasks);useReducer 和 useState 很相似——必须给它传递一个初始状态它会返回一个有状态的值和一个设置该状态的函数在这个例子中就是 dispatch 函数。但是它们两个之间还是有点差异的。
useReducer 钩子接受 2 个参数
一个 reducer 函数一个初始的 state
它返回如下内容
一个有状态的值一个 dispatch 函数用来 “派发” 用户操作给 reducer
现在一切都准备就绪了在这里把 reducer 定义在了组件的末尾
import { useReducer } from react;
import AddTask from ./AddTask.js;
import TaskList from ./TaskList.js;export default function TaskApp() {const [tasks, dispatch] useReducer(tasksReducer, initialTasks);function handleAddTask(text) {dispatch({type: added,id: nextId,text: text,});}function handleChangeTask(task) {dispatch({type: changed,task: task,});}function handleDeleteTask(taskId) {dispatch({type: deleted,id: taskId,});}return (h1布拉格的行程安排/h1AddTask onAddTask{handleAddTask} /TaskListtasks{tasks}onChangeTask{handleChangeTask}onDeleteTask{handleDeleteTask}//);
}function tasksReducer(tasks, action) {switch (action.type) {case added: {return [...tasks,{id: action.id,text: action.text,done: false,},];}case changed: {return tasks.map((t) {if (t.id action.task.id) {return action.task;} else {return t;}});}case deleted: {return tasks.filter((t) t.id ! action.id);}default: {throw Error(未知 action: action.type);}}
}...如果有需要甚至可以把 reducer 移到一个单独的文件中
// tasksReducer.js
export default function tasksReducer(tasks, action) {switch (action.type) {case added: {return [...tasks,{id: action.id,text: action.text,done: false,},];}case changed: {return tasks.map((t) {if (t.id action.task.id) {return action.task;} else {return t;}});}case deleted: {return tasks.filter((t) t.id ! action.id);}default: {throw Error(未知 action action.type);}}
}当像这样分离关注点时我们可以更容易地理解组件逻辑。现在事件处理程序只通过派发 action 来指定 发生了什么而 reducer 函数通过响应 actions 来决定 状态如何更新。
对比 useState 和 useReducer
Reducers 并非没有缺点以下是比较它们的几种方法
代码体积 通常在使用 useState 时一开始只需要编写少量代码。而 useReducer 必须提前编写 reducer 函数和需要调度的 actions。但是当多个事件处理程序以相似的方式修改 state 时useReducer 可以减少代码量。可读性 当状态更新逻辑足够简单时useState 的可读性还行。但是一旦逻辑变得复杂起来它们会使组件变得臃肿且难以阅读。在这种情况下useReducer 允许将状态更新逻辑与事件处理程序分离开来。可调试性 当使用 useState 出现问题时, 很难发现具体原因以及为什么。 而使用 useReducer 时 可以在 reducer 函数中通过打印日志的方式来观察每个状态的更新以及为什么要更新来自哪个 action。 如果所有 action 都没问题就知道问题出在了 reducer 本身的逻辑中。 然而与使用 useState 相比必须单步执行更多的代码。可测试性 reducer 是一个不依赖于组件的纯函数。这就意味着可以单独对它进行测试。一般来说我们最好是在真实环境中测试组件但对于复杂的状态更新逻辑针对特定的初始状态和 action断言 reducer 返回的特定状态会很有帮助。个人偏好 并不是所有人都喜欢用 reducer没关系这是个人偏好问题。可以随时在 useState 和 useReducer 之间切换它们能做的事情是一样的
如果在修改某些组件状态时经常出现问题或者想给组件添加更多逻辑时建议还是使用 reducer。当然也不必整个项目都用 reducer这是可以自由搭配的。甚至可以在一个组件中同时使用 useState 和 useReducer。
编写一个好的 reducers
编写 reducers 时最好牢记以下两点
reducers 必须是纯粹的。 这一点和 状态更新函数 是相似的reducers 在是在渲染时运行的actions 会排队直到下一次渲染)。 这就意味着 reducers 必须纯净即当输入相同时输出也是相同的。它们不应该包含异步请求、定时器或者任何副作用对组件外部有影响的操作。它们应该以不可变值的方式去更新 对象 和 数组。每个 action 都描述了一个单一的用户交互即使它会引发数据的多个变化。 举个例子如果用户在一个由 reducer 管理的表单包含五个表单项中点击了 重置按钮那么 dispatch 一个 reset_form 的 action 比 dispatch 五个单独的 set_field 的 action 更加合理。如果在一个 reducer 中打印了所有的 action 日志那么这个日志应该是很清晰的它能让你以某种步骤复现已发生的交互或响应。这对代码调试很有帮助
使用 Immer 简化 reducers
与在平常的 state 中 修改对象 和 数组 一样可以使用 Immer 这个库来简化 reducer。在这里useImmerReducer 让你可以通过 push 或 arr[i] 来修改 state
import { useImmerReducer } from use-immer;
import AddTask from ./AddTask.js;
import TaskList from ./TaskList.js;function tasksReducer(draft, action) {switch (action.type) {case added: {draft.push({id: action.id,text: action.text,done: false,});break;}case changed: {const index draft.findIndex((t) t.id action.task.id);draft[index] action.task;break;}case deleted: {return draft.filter((t) t.id ! action.id);}default: {throw Error(未知 action action.type);}}
}export default function TaskApp() {const [tasks, dispatch] useImmerReducer(tasksReducer, initialTasks);function handleAddTask(text) {dispatch({type: added,id: nextId,text: text,});}function handleChangeTask(task) {dispatch({type: changed,task: task,});}function handleDeleteTask(taskId) {dispatch({type: deleted,id: taskId,});}return (h1布拉格的行程安排/h1AddTask onAddTask{handleAddTask} /TaskListtasks{tasks}onChangeTask{handleChangeTask}onDeleteTask{handleDeleteTask}//);
}...Reducers 应该是纯净的所以它们不应该去修改 state。而 Immer 提供了一种特殊的 draft 对象可以通过它安全的修改 state。在底层Immer 会基于当前 state 创建一个副本。这就是为什么通过 useImmerReducer 来管理 reducers 时可以修改第一个参数且不需要返回一个新的 state 的原因。
摘要
把 useState 转化为 useReducer 通过事件处理函数 dispatch actions编写一个 reducer 函数它接受传入的 state 和一个 action并返回一个新的 state使用 useReducer 替换 useState
Reducers 可能需要写更多的代码但是这有利于代码的调试和测试。Reducers 必须是纯净的。每个 action 都描述了一个单一的用户交互。使用 Immer 来帮助在 reducer 里直接修改状态。