- ReactJS 教程
- ReactJS - 首页
- ReactJS - 简介
- ReactJS - 路线图
- ReactJS - 安装
- ReactJS - 特性
- ReactJS - 优点与缺点
- ReactJS - 架构
- ReactJS - 创建 React 应用
- ReactJS - JSX
- ReactJS - 组件
- ReactJS - 嵌套组件
- ReactJS - 使用新创建的组件
- ReactJS - 组件集合
- ReactJS - 样式
- ReactJS - 属性 (props)
- ReactJS - 使用属性创建组件
- ReactJS - props 验证
- ReactJS - 构造函数
- ReactJS - 组件生命周期
- ReactJS - 事件管理
- ReactJS - 创建一个事件感知组件
- ReactJS - 在 Expense Manager 应用中引入事件
- ReactJS - 状态管理
- ReactJS - 状态管理 API
- ReactJS - 无状态组件
- ReactJS - 使用 React Hooks 进行状态管理
- ReactJS - 使用 React Hooks 进行组件生命周期管理
- ReactJS - 布局组件
- ReactJS - 分页
- ReactJS - Material UI
- ReactJS - Http 客户端编程
- ReactJS - 表单编程
- ReactJS - 受控组件
- ReactJS - 非受控组件
- ReactJS - Formik
- ReactJS - 条件渲染
- ReactJS - 列表
- ReactJS - Keys
- ReactJS - 路由
- ReactJS - Redux
- ReactJS - 动画
- ReactJS - Bootstrap
- ReactJS - 地图
- ReactJS - 表格
- ReactJS - 使用 Flux 管理状态
- ReactJS - 测试
- ReactJS - CLI 命令
- ReactJS - 构建和部署
- ReactJS - 示例
- Hooks
- ReactJS - Hooks 简介
- ReactJS - 使用 useState
- ReactJS - 使用 useEffect
- ReactJS - 使用 useContext
- ReactJS - 使用 useRef
- ReactJS - 使用 useReducer
- ReactJS - 使用 useCallback
- ReactJS - 使用 useMemo
- ReactJS - 自定义 Hooks
- ReactJS 高级
- ReactJS - 可访问性
- ReactJS - 代码分割
- ReactJS - Context
- ReactJS - 错误边界
- ReactJS - 转发 Refs
- ReactJS - 片段
- ReactJS - 高阶组件
- ReactJS - 集成其他库
- ReactJS - 优化性能
- ReactJS - Profiler API
- ReactJS - 端口
- ReactJS - 无 ES6 ECMAScript 的 React
- ReactJS - 无 JSX 的 React
- ReactJS - 调和
- ReactJS - Refs 和 DOM
- ReactJS - 渲染 Props
- ReactJS - 静态类型检查
- ReactJS - 严格模式
- ReactJS - Web Components
- 其他概念
- ReactJS - 日期选择器
- ReactJS - Helmet
- ReactJS - 内联样式
- ReactJS - PropTypes
- ReactJS - BrowserRouter
- ReactJS - DOM
- ReactJS - 走马灯
- ReactJS - 图标
- ReactJS - 表单组件
- ReactJS - 参考 API
- ReactJS 有用资源
- ReactJS - 快速指南
- ReactJS - 有用资源
- ReactJS - 讨论
ReactJS - 使用 useReducer
useReducer hook 是 useState hook 的高级版本。众所周知,useState 的目的是管理状态变量。useState 返回一个函数,该函数接受一个值并使用给定值更新状态变量。
// counter = 0 const [counter, setCounter] = useState(0) // counter = 1 setCounter(1) // counter = 2 setCounter(2)
useReducer hook 接受一个 reducer 函数以及初始值,并返回一个调度程序函数。Reducer 函数将接受初始状态和一个 action(特定场景),然后提供根据 action 更新状态的逻辑。调度程序函数接受 action(以及相应的详细信息)并使用提供的 action 调用 reducer 函数。
例如,useReducer 可用于根据增量和减量 action 更新计数器状态。增量 action 将计数器状态加 1,减量 action 将计数器状态减 1。
让我们在本节中学习如何在 React 中使用 useReducer hook。
useReducer hook 的签名
useReducer hook 的签名如下所示:
const [<state>, <dispatch function>] = useReducer(<reducer function>, <initial argument>, <init function>);
这里,
state 表示要在状态中维护的信息
reducer 函数是一个 JavaScript 函数,用于根据 action 更新状态。
以下是 reducer 函数的语法:
(<state>, <action>) => <updated state>
其中,
state - 当前状态信息
action - 要执行的操作(应有有效负载来执行操作)
更新后的状态 - 更新后的状态
初始参数 - 表示状态的初始值。
init 函数 - 表示初始化函数,可用于设置状态的初始值/重置状态的当前值。如果需要计算初始值,则可以使用 init 函数。否则,可以跳过该参数。
应用 reducer hook
让我们创建一个 React 应用来管理待办事项的集合。首先,我们将使用 useState 实现它,然后将其转换为使用 useReducer。通过使用这两个 hook 实现应用程序,我们将了解 useReducer 相比 useState 的优势。此外,我们还可以根据情况明智地选择 hook。
首先,创建一个新的 React 应用并使用以下命令启动它。
create-react-app myapp cd myapp npm start
接下来,在 component 文件夹下(src/components/TodoList.js)创建一个 React 组件 TodoList。
function TodoList() { return <div>Todo List</div> } export default TodoList
接下来,更新根组件 App.js 以使用新创建的 TodoList 组件。
import logo from './logo.svg'; import './App.css'; import TodoList from './components/TodoList'; function App() { return ( <div style={{ padding: "5px"}}> <TodoList /> </div> ); } export default App;
接下来,创建一个变量 todoData 来管理待办事项列表,并使用 useState hook 将其设置为状态。
const [todoData, setTodoData] = useState({ action: '', items: [], newItem: null, id: 0 })
这里,
action 用于表示要应用于当前待办事项列表 (items) 的当前操作,add 和 delete。
items 是一个用于保存当前待办事项列表的数组。
newItem 是一个用于表示当前待办事项的对象。该对象将有两个字段,id 和 todo。
id 是在删除操作期间要删除的当前项目的 ID。
useState 用于获取和设置待办事项列表 (todoData)。
接下来,渲染当前待办事项列表 (todoData.items),以及一个删除按钮和一个输入文本字段以输入新的待办事项。
<div> <p>List of Todo list</p> <ul> {todoData.items && todoData.items.map((item) => <li key={item.id}>{item.todo} <span><button onClick={(e) => handleDeleteButton(item.id, e)}>Delete</button></span></li> )} <li><input type="text" name="todo" onChange={handleInput} /> <button onClick={handleAddButton}>Add</button></li> </ul> </div>
这里,
从状态变量 todoData 渲染当前待办事项列表。
渲染一个输入字段,供用户输入新的待办事项,并附加 onChange 事件处理程序 handleInput。事件处理程序将使用用户在输入字段中输入的数据更新 todo 状态 (todoData) 中的 newItem。
渲染一个按钮,供用户将新输入的待办事项添加到当前待办事项列表中,并附加 onClick 事件处理程序 handleAddButton。事件处理程序将把当前/新的待办事项添加到待办事项状态中。
为待办事项列表中的每个项目渲染一个按钮,并附加 onClick 事件处理程序 handleDeleteButton。事件处理程序将从待办事项状态中删除相应的待办事项。
接下来,实现 handleInput 事件处理程序,如下所示:
const handleInput = (e) => { var id = 0 if(todoData.newItem == null) { for(let i = 0; i < todoData.items.length; i++) { if(id < todoData.items[i].id) { id = todoData.items[i].id } } id += 1 } else { id = todoData.newItem.id } let data = { actions: '', items: todoData.items, newItem: { id: id, todo: e.target.value }, id: 0 } setTodoData(data) }
这里我们有,
使用用户输入的数据 (e.target.value) 更新 newItem.todo
创建并设置新项目的 ID。
接下来,实现 handleDeleteButton 事件处理程序,如下所示:
const handleDeleteButton = (deleteId, e) => { let data = { action: 'delete', items: todoData.items, newItem: todoData.newItem, id: deleteId } setTodoData(data) }
在这里,处理程序将要删除的待办事项的 ID (deleteid) 和 delete 操作设置为待办事项状态。
接下来,实现 handleAddButton 事件处理程序,如下所示:
const handleAddButton = () => { let data = { action: 'add', items: todoData.items, newItem: todoData.newItem, id: 0 } setTodoData(data) }
在这里,处理程序在状态中设置新项目和 add 操作。
接下来,实现 add 操作,如下所示:
if(todoData.action == 'add') { if(todoData.newItem != null) { let data = { action: '', items: [...todoData.items, todoData.newItem], newItem: null, id: 0 } setTodoData(data) } }
在这里,新项目添加到待办事项状态中现有的列表 (todoData.items) 中。
接下来,实现 delete 操作,如下所示:
if(todoData.action == 'delete' && todoData.id != 0) { var newItemList = [] for(let i = 0; i < todoData.items.length; i++) { if(todoData.items[i].id != todoData.id) { newItemList.push(todoData.items[i]) } } let data = { action: '', items: newItemList, newItem: null, id: 0 } setTodoData(data) }
在这里,从待办事项列表 (todoData.items) 中删除了指定 ID 的项目。
组件的完整源代码如下所示:
import { useState } from "react" function TodoList() { const [todoData, setTodoData] = useState({ action: '', items: [], newItem: null, id: 0 }) if(todoData.action == 'add') { if(todoData.newItem != null) { let data = { action: '', items: [...todoData.items, todoData.newItem], newItem: null, id: 0 } setTodoData(data) } } if(todoData.action == 'delete' && todoData.id != 0) { var newItemList = [] for(let i = 0; i < todoData.items.length; i++) { if(todoData.items[i].id != todoData.id) { newItemList.push(todoData.items[i]) } } let data = { action: '', items: newItemList, newItem: null, id: 0 } setTodoData(data) } const handleInput = (e) => { var id = 0 if(todoData.newItem == null) { for(let i = 0; i < todoData.items.length; i++) { if(id < todoData.items[i].id) { id = todoData.items[i].id } } id += 1 } else { id = todoData.newItem.id } let data = { action: '', items: todoData.items, newItem: { id: id, todo: e.target.value }, id: 0 } setTodoData(data) } const handleDeleteButton = (deleteId, e) => { let data = { action: 'delete', items: todoData.items, newItem: todoData.newItem, id: deleteId } setTodoData(data) } const handleAddButton = () => { let data = { action: 'add', items: todoData.items, newItem: todoData.newItem, id: 0 } setTodoData(data) } return ( <div> <p>List of Todo list</p> <ul> {todoData.items && todoData.items.map((item) => <li key={item.id}>{item.todo} <span><button onClick={(e) => handleDeleteButton(item.id, e)}>Delete</button></span></li> )} <li><input type="text" name="todo" onChange={handleInput} /><button onClick={handleAddButton}>Add</button></li> </ul> </div> ) } export default TodoList
这里,应用程序使用 useState hook 来实现功能。
接下来,打开浏览器并添加/删除待办事项。应用程序将按如下所示运行:
使用 useReducer
让我们使用 useReducer 重新实现该功能。
首先,在 component 文件夹下(src/components/TodoReducerList.js)创建一个 React 组件 TodoReducerList。
function TodoReducerList() { return <div>Todo List</div> } export default TodoReducerList
接下来,更新根组件 App.js 以使用新创建的 TodoReducerList 组件。
import './App.css'; import TodoReducerList from './components/TodoReducerList'; function App() { return ( <div style={{ padding: "5px"}}> <TodoReducerList /> </div> ); } export default App;
接下来,实现一个 reducer 函数 todoReducer,它将接收两个参数,如下所示:
当前待办事项列表 (items)
操作 (action.type) 以及与操作相关的信息 (action.payload)。对于添加操作 (action.type),有效负载 (action.payload) 将包含新的待办事项,对于删除操作 (action.type),它将包含要删除的待办事项的 ID。
reducer 将对待办事项列表应用相关操作,并返回修改后的待办事项列表,如下所示:
function todoReducer(items, action) { // action = { type: 'add / delete', payload: 'new todo item'} let newTodoList = [] switch (action.type) { case 'add': var id = 0 for(let i = 0; i < items.length; i++) { if(id < items[i].id) { id = items[i].id } } action.payload.id = id + 1 newTodoList = [...items, action.payload] break; case 'delete': for(let i = 0; i < items.length; i++) { if(items[i].id != action.payload.id) { newTodoList.push(items[i]) } } break; default: throw new Error() } return newTodoList }
这里我们有,
使用 switch 语句处理操作。
Added 操作将有效负载添加到现有待办事项列表中。
deleted 操作从现有待办事项列表中删除有效负载中指定的项目。
最后,函数返回更新后的待办事项列表。
接下来,在 TodoReducerList 组件中使用新创建的 reducer,如下所示:
const [items, dispatch] = useReducer(todoReducer, [])
这里,useReducer 接收两个参数:a) reducer 函数和 b) 当前待办事项列表,并返回当前待办事项列表和一个调度程序函数 dispatch。调度程序函数 (dispatch) 应使用相关有效负载信息与添加或删除操作一起调用。
接下来,为输入文本字段(新的待办事项)和两个按钮(添加和删除)创建处理程序函数,如下所示:
const [todo, setTodo] = useState('') const handleInput = (e) => { setTodo(e.target.value) } const handleAddButton = () => { dispatch({ type: 'add', payload: { todo: todo } }) setTodo('') } const handleDeleteButton = (id) => { dispatch({ type: 'delete', payload: { id: id } }) }
这里我们有,
使用 useState 管理用户输入 (todo)。
使用 dispatch 方法在相关的处理程序中处理 add 和 delete 操作。
在处理程序中传递特定于操作的有效负载。
接下来,更新渲染方法,如下所示:
<div> <p>List of Todo list</p> <ul> {items && items.map((item) => <li key={item.id}>{item.todo} <span><button onClick={(e) => handleDeleteButton(item.id)}>Delete</button></span></li> )} <li><input type="text" name="todo" value={todo} onChange={handleInput} /> <button onClick={handleAddButton}>Add</button></li> </ul> </div>
组件的完整源代码以及 reducer 函数如下所示:
import { useReducer, useState } from "react" function todoReducer(items, action) { // action = { type: 'add / delete', payload: 'new todo item'} let newTodoList = [] switch (action.type) { case 'add': var id = 0 for(let i = 0; i < items.length; i++) { if(id < items[i].id) { id = items[i].id } } action.payload.id = id + 1 newTodoList = [...items, action.payload] break; case 'delete': for(let i = 0; i < items.length; i++) { if(items[i].id != action.payload.id) { newTodoList.push(items[i]) } } break; default: throw new Error() } return newTodoList } function TodoReducerList() { const [todo, setTodo] = useState('') const [items, dispatch] = useReducer(todoReducer, []) const handleInput = (e) => { setTodo(e.target.value) } const handleAddButton = () => { dispatch({ type: 'add', payload: { todo: todo } }) setTodo('') } const handleDeleteButton = (id) => { dispatch({ type: 'delete', payload: { id: id } }) } return ( <div> <p>List of Todo list</p> <ul> {items && items.map((item) => <li key={item.id}>{item.todo} <span><button onClick={(e) => handleDeleteButton(item.id)}>Delete</button></span></li> )} <li><input type="text" name="todo" value={todo} onChange={handleInput} /> <button onClick={handleAddButton}>Add</button></li> </ul> </div> ) } export default TodoReducerList
接下来,打开浏览器并检查输出。
我们可以清楚地理解,与纯 useState 实现相比,useReducer 实现更简单、更容易理解。
总结
useReducer 钩子在状态管理中引入了 reducer 模式。它鼓励代码复用,并提高组件的可读性和可理解性。总的来说,useReducer 是 React 开发者工具箱中一个必不可少且强大的工具。