ReactJS - 使用 useReducer
useReducer Hooks(钩子)是 useState Hooks(钩子)的高级版本。众所周知,useState 的目的是管理状态变量。useState 返回一个函数,该函数接受一个值并使用给定的值更新状态变量。
// counter = 0 const [counter, setCounter] = useState(0) // counter = 1 setCounter(1) // counter = 2 setCounter(2)
useReducer Hooks(钩子)接受一个 Reducer 函数以及初始值并返回一个调度器函数。Reducer 函数将接受初始状态和一个操作(特定场景),然后提供逻辑以根据操作更新状态。调度器函数接受操作(和相应的详细信息)并使用提供的操作调用 Reducer 函数。
例如,useReducer 可用于根据增量和减量操作更新计数器状态。增加操作将使计数器状态增加 1,减少操作将使计数器状态减少 1。
本章让我们学习如何使用 useReducer Hooks(钩子)。
useReducer Hooks(钩子)的签名
useReducer Hooks(钩子)的签名如下 −
const [<state>, <dispatch function>] = useReducer(<reducer function>, <initialargument>, <init function>);
这里,
state表示状态中需要维护的信息
reducer函数是一个javascript函数,用于根据action更新状态。
以下是reducer函数−的语法
(<state>, <action>) => <updated state>
其中,
state − 当前状态信息
action −要执行的操作(应具有执行操作的有效负载)
更新状态 − 更新状态
初始参数 −表示状态的初始值
init 函数 −表示初始化函数,可用于设置初始值/重置状态的当前值。如果需要计算初始值,则可以使用 init 函数。否则,可以跳过该参数。
应用 Reducer Hooks(钩子)
让我们创建一个 React 应用程序来管理待办事项集合。首先,我们将使用 useState 实现它,然后将其转换为使用 useReducer。通过使用两个Hooks(钩子)实现应用程序,我们将了解 useReducer 相对于 useState 的优势。此外,我们可以根据情况明智地选择Hooks(钩子)。
首先,创建一个新的 React 应用程序并使用以下命令启动它。
create-react-app myapp cd myapp npm start
接下来,在组件文件夹 (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 Hooks(钩子)将其设置为状态。
const [todoData, setTodoData] = useState({
action: '',
items: [],
newItem: null,
id: 0
})
此处,
action 用于表示当前操作,add 和 delete 将应用于当前待办事项列表 (items)。
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。事件处理程序将使用用户在输入字段中输入的数据更新待办事项状态 (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操作,如下所示 −
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 Hooks(钩子)来实现该功能。
接下来,打开浏览器并添加/删除待办事项。应用程序将按如下所示运行 −
使用 useReducer
让我们使用 useReducer
重新实现该功能首先,在组件文件夹 (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 来管理用户输入(待办事项)
使用 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 hook 在状态管理中引入了 Reducer 模式。它鼓励代码重用并提高组件的可读性和可理解性。总的来说,useReducer 是 React 开发人员工具包中必不可少且功能强大的工具。

