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) 的当前操作,adddelete

  • items 是一个用于保存当前待办事项列表的数组。

  • newItem 是一个用于表示当前待办事项的对象。该对象将有两个字段,idtodo

  • 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 来实现功能。

接下来,打开浏览器并添加/删除待办事项。应用程序将按如下所示运行:

Applying Reducer 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 方法在相关的处理程序中处理 adddelete 操作。

  • 在处理程序中传递特定于操作的有效负载。

接下来,更新渲染方法,如下所示:

<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

接下来,打开浏览器并检查输出。

Using UseReducer

我们可以清楚地理解,与纯 useState 实现相比,useReducer 实现更简单、更容易理解。

总结

useReducer 钩子在状态管理中引入了 reducer 模式。它鼓励代码复用,并提高组件的可读性和可理解性。总的来说,useReducer 是 React 开发者工具箱中一个必不可少且强大的工具。

广告