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 Hooks 进行状态管理 ReactJS Hooks 的组件生命周期 ReactJS 布局组件 ReactJS 分页 ReactJS Material UI ReactJS Http 客户端编程 ReactJS 表单编程 ReactJS 受控组件 ReactJS 非受控组件 ReactJS Formik ReactJS 条件渲染 ReactJS 列表 ReactJS Key 键 ReactJS 路由 ReactJS Redux ReactJS 动画 ReactJS Bootstrap ReactJS Map 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 上下文 ReactJS 错误边界 ReactJS 转发 Refs ReactJS 片段 ReactJS 高阶组件 ReactJS 与其他库集成 ReactJS 优化性能 ReactJS Profiler API ReactJS Portals ReactJS 不使用 ES6 ECMAScript ReactJS 不使用 JSX 的 React ReactJS Reconciliation ReactJS Refs 和 DOM ReactJS 渲染道具 ReactJS 静态类型检查 ReactJS 严格模式 ReactJS Web 组件

其他概念

ReactJS 日期选择器 ReactJS Helmet ReactJS 内联样式 ReactJS PropTypes ReactJS BrowserRouter ReactJS DOM ReactJS 轮播 ReactJS 图标 ReactJS 表单组件 ReactJS 参考 API

ReactJS 有用资源

ReactJS 快速指南 ReactJS 备忘录 Axios 备忘录 ReactJS 有用资源 ReactJS 讨论


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(钩子)来实现该功能。

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

Applying Reducer Hook

使用 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

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

使用 UseReducer

我们可以清楚地看到,与纯 useState 实现相比,useReducer 实现简单、易懂且易于理解。

总结

useReducer hook 在状态管理中引入了 Reducer 模式。它鼓励代码重用并提高组件的可读性和可理解性。总的来说,useReducer 是 React 开发人员工具包中必不可少且功能强大的工具。