前言

这次我们使用 React Hooks 来实现派发器思想

其实 React Hooks 对派发器思想的支持非常友好

因为他自带的 useReducer 的使用方式刚好符合派发器思想这一特点

所以说是 React Hooks 实现派发器思想

不如说是 useReducer 使用教程

这里我们依然使用万能的 TodoList 来做例子

项目初始换

这里我们使用 Vite 当作 dev 工具来展示

在终端中输入 yarn create vite 来初始化我们的项目

框架我们选择 React

后面使用 JS TS 都行看个人选择

我这边就先选择 JS 来做展示

然后进入到目录,安装 node_modules

修改目录

首先清理文件

把不必要的文件删除就可以

最后保留的像我这样即可

派发器思想-1.png

修改 App.jsx

  1. function App() {
  2. return (
  3. <div>
  4. </div>
  5. )
  6. }
  7. export default App

创建目录

首先我们把我们需要的目录创建好

在src目录下创建以下文件及文件夹

  • components
    • TodoList - 文件夹
      • index.jsx
      • Form.jsx
      • List.jsx
      • ListItem.jsx
      • store - 文件夹
        • index.js - 入口文件
        • actions.js - reducer 需要用到的函数
        • actionTypes.js - 触发 action 的标志
        • reducer.js - action 的集合,用来派发事件使用
        • state.js - 变量

都创建完成之后大概就是这样子

派发器思想-2.png

components目录文件初始化

JSX 文件初始化

首先我们先把大框架给搭好再写内容

把每个 components 下的 JSX 文件像这样写好

这里复制粘贴即可

就算不改函数名问题也不大,改了是可以更好的区分内容的

  1. function Index /* 对应文件夹的名字 */() {
  2. return (
  3. <div>
  4. </div>
  5. )
  6. }
  7. export default Index

store 文件初始化

state.js

这个文件只是用来保存变量的

所以不用太复杂

  1. export default {
  2. }

actions.js

  1. export default function (state) {
  2. return {
  3. }
  4. }

actionTypes.js 暂时先空着

reducer.js

  1. import initialState from "./state"
  2. import action from "./action"
  3. import { } from "./actionType"
  4. const { useReducer } = React
  5. function reducer(state, { type, payload }) {
  6. const { } = action(state)
  7. switch (type) {
  8. default:
  9. return state;
  10. }
  11. }
  12. export default function () {
  13. return useReducer(reducer, initialState)
  14. }

index.js

  1. import todoReducer from "./reducer"
  2. export { } from "./actionType"

分析需求

一个基本的 TodoList 需要带有三种功能

  • 添加 Todo
  • 删除 Todo
  • 切换 Todo 状态

这样我们就得到了三个基本需求

在深入点的分析 Todo 需要的东西

  • Todo
    • 提供一个 content 来表示需要做什么事情
    • 提供一个 id 来做唯一表示,因为 content 可能有会重复
    • 提供一个 completed 状态来表示是否完成

ok 到此为止,我们已经基本确定一个 TodoList 需要的东西了

一个的 TodoItem 对象需要的东西

  1. {
  2. id: 0, // 我们用时间戳做唯一表示
  3. content: '', // 表示需要做的事情的内容
  4. completed: false, // 表示此 Todo 是否已经完成
  5. }

App.jsx 完成

  1. import TodoList from "./components/TodoList"
  2. // 只需要引入组件即可 所有的逻辑都在组件里完成
  3. function App() {
  4. return (
  5. <div>
  6. <TodoList />
  7. </div>
  8. )
  9. }
  10. export default App

TodoList > index.jsx 视图与部分逻辑完成

  1. import Form from "./Form"
  2. import List from "./List"
  3. // 这里什么逻辑也没有只是先引入组件
  4. function TodoList() {
  5. return (
  6. <div>
  7. <Form />
  8. <List />
  9. </div>
  10. )
  11. }
  12. export default TodoList

Form.jsx 视图与部分逻辑完成

首先我们肯定要先展示视图

在 src/App.jsx 引入 TodoList 并展示

  1. import TodoList from "./components/TodoList"
  2. function App() {
  3. return (
  4. <div>
  5. <TodoList />
  6. </div>
  7. )
  8. }
  9. export default App

在 src/components/index.jsx 引入 Form.jsx 并展示

  1. import Form from "./Form"
  2. function Index() {
  3. return (
  4. <div>
  5. <Form />
  6. </div>
  7. )
  8. }
  9. export default Index

编写 Form.jsx 视图

  1. function Form() {
  2. return (
  3. <div>
  4. {/* content 输入框 */}
  5. <input
  6. type={'text'}
  7. placeholder={'please input something...'}
  8. />
  9. {/* 触发 添加Todo 事件的按钮 */}
  10. <button>ADD</button>
  11. </div>
  12. )
  13. }
  14. export default Form

编写 Form.jsx 部分逻辑

  1. import { useState } from "react"
  2. function Form() {
  3. // Todo.content
  4. const [content, setContent] = useState('')
  5. const addTodo = () => {
  6. if (content.length === 0) {
  7. alert('请输入需要做的内容')
  8. return
  9. }
  10. const todo = {
  11. content,
  12. id: Date.now(), // 用当前时间戳来做 ID
  13. completed: false, // 默认为未完成状态
  14. }
  15. }
  16. return (
  17. <div>
  18. {/* content 输入框 */}
  19. <input
  20. type={'text'}
  21. placeholder={'please input something...'}
  22. value={content}
  23. onChange={event => setContent(event.target.value)} // 更改 content
  24. />
  25. {/* 触发 添加Todo 事件的按钮 */}
  26. <button>ADD</button>
  27. </div>
  28. )
  29. }
  30. export default Form

List.jsx

  1. import ListItem from "./ListItem"
  2. function List({
  3. // 先定义好需要从父组件获取的数据
  4. todoList = 0,
  5. addCount = 0,
  6. removeCount = [],
  7. }) {
  8. return (
  9. <div>
  10. <p>已添加过{addCount}条</p>
  11. <p>已删除过{removeCount}条</p>
  12. <ul>
  13. {
  14. todoList.map(item => (
  15. <ListItem
  16. data={item}
  17. key={item.id}
  18. />
  19. ))
  20. }
  21. </ul>
  22. </div>
  23. )
  24. }
  25. export default List

ListItem.jsx 视图与部分逻辑完成

也跟上面同样的道理

先定义好数据

  1. function ListItem({ data }) {
  2. return (
  3. <li>
  4. <input
  5. type="checkbox"
  6. checked={data.completed}
  7. />
  8. <span style={{ textDecoration: data.completed && 'line-through' }}>
  9. {data.content}
  10. </span>
  11. <button onClick={() => dispatch({ type: REMOVE_TODO, payload: data.id })}>
  12. REMOVE
  13. </button>
  14. </li>
  15. )
  16. }
  17. export default ListItem

编写 store 的数据与逻辑

state.js

这只是个存放数据的地方

  1. export default {
  2. todoList: [], // 当前的 Todo 列表
  3. addCount: 0, // 一共添加过多少条数据
  4. removeCount: 0, // 一共删除过多少条数据
  5. }

actionTypes.js

  1. export const
  2. ADD_TODO = 'ADD_TODO', // 对应添加 Todo 的操作
  3. REMOVE_TODO = 'REMOVE_TODO', // 对应删除 Todo 的操作
  4. TOGGLE_TODO = 'TOGGLE_TODO' // 对应切换 Todo 状态的操作

action.js

  1. export default function (
  2. state // 接收 state 的数据
  3. ) {
  4. // 添加 Todo 的事件
  5. const onAddTodo = (todo) => {
  6. return {
  7. ...state,
  8. todoList: [...state.todoList, todo],
  9. addCount: state.addCount + 1
  10. }
  11. }
  12. // 切换 Todo 状态的事件
  13. const onToggleTodo = (id) => {
  14. const todoList = state.todoList.map(item => (
  15. item.id === id && (item.completed = !item.completed),
  16. item
  17. ))
  18. return {
  19. ...state,
  20. todoList
  21. }
  22. }
  23. // 删除 Todo 的事件
  24. const onRemoveTodo = (id) => {
  25. return {
  26. ...state,
  27. todoList: state.todoList.filter(item => item.id !== id),
  28. removeCount: state.removeCount + 1
  29. }
  30. }
  31. return {
  32. onAddTodo,
  33. onToggleTodo,
  34. onRemoveTodo,
  35. }
  36. }

归纳到 reducer.js 中进行集中派发

  1. import initialState from "./state"
  2. import action from "./action"
  3. import {
  4. ADD_TODO,
  5. TOGGLE_TODO,
  6. REMOVE_TODO,
  7. } from "./actionType"
  8. // 这里我们使用 React 的 useReducer 进行派发
  9. const { useReducer } = React
  10. function reducer(
  11. state, // 数据
  12. {
  13. type, // 触发事件的 type
  14. payload, // 传入的参数
  15. }
  16. ) {
  17. const {
  18. onAddTodo,
  19. onToggleTodo,
  20. onRemoveTodo,
  21. } = action(state)
  22. switch (type) {
  23. case ADD_TODO:
  24. return onAddTodo(payload);
  25. case TOGGLE_TODO:
  26. return onToggleTodo(payload);
  27. case REMOVE_TODO:
  28. return onRemoveTodo(payload);
  29. default:
  30. return state;
  31. }
  32. }
  33. export default function () {
  34. return useReducer(reducer, initialState)
  35. }

最后通过 index.js 进行集中导出

  1. import todoReducer from "./reducer"
  2. import {
  3. ADD_TODO,
  4. REMOVE_TODO,
  5. TOGGLE_TODO,
  6. } from "./actionTypes"
  7. export {
  8. ADD_TODO,
  9. REMOVE_TODO,
  10. TOGGLE_TODO,
  11. todoReducer,
  12. }

现在我们在 index.jsx 去使用他

  1. import FormComp from "./Form"
  2. import ListComp from "./List"
  3. import { todoReducer } from "./store"
  4. import { createContext } from 'react'
  5. /**
  6. * react 的上下文函数
  7. * 可以使所有包裹的子组件通过 useContext 去使用里面的数据或者函数
  8. */
  9. export const ListContext = createContext({})
  10. function TodoList() {
  11. const [
  12. {
  13. todoList,
  14. addCount,
  15. removeCount,
  16. },
  17. dispatch, // 派发器函数
  18. ] = todoReducer()
  19. return (
  20. <div>
  21. <FormComp dispatch={dispatch} />
  22. <ListContext.Provider
  23. value={{ dispatch }}
  24. >
  25. <ListComp
  26. addCount={addCount}
  27. removeCount={removeCount}
  28. todoList={todoList}
  29. />
  30. </ListContext.Provider>
  31. </div>
  32. )
  33. }
  34. export default TodoList

Form.jsx 的逻辑编写

  1. import { useState } from "react"
  2. import { ADD_TODO } from "./store"
  3. function Form({ dispatch }) {
  4. const [content, setContent] = useState('')
  5. const addTodo = () => {
  6. if (content.length === 0) {
  7. alert('请输入需要做的内容')
  8. return
  9. }
  10. const todo = {
  11. content,
  12. id: Date.now(),
  13. completed: false,
  14. }
  15. // 发送 ADD_TODO指令和payload参数 到派发器中进行处理
  16. dispatch({
  17. type: ADD_TODO,
  18. payload: todo,
  19. })
  20. }
  21. return (
  22. <div>
  23. <input
  24. type={'text'}
  25. placeholder={'please input something...'}
  26. value={content}
  27. onChange={event => setContent(event.target.value)}
  28. />
  29. <button onClick={addTodo}>ADD</button>
  30. </div >
  31. )
  32. }
  33. export default Form

ListItem.jsx 逻辑编写

  1. import { ListContext } from "./index"
  2. import { REMOVE_TODO, TOGGLE_TODO } from "./store"
  3. import { useContext } from "react"
  4. function ListItem({ data }) {
  5. const { dispatch } = useContext(ListContext)
  6. return (
  7. <li>
  8. <input
  9. type="checkbox"
  10. checked={data.completed}
  11. onChange={() => dispatch({ type: TOGGLE_TODO, payload: data.id })}
  12. />
  13. <span style={{ textDecoration: data.completed && 'line-through' }}>
  14. {data.content}
  15. </span>
  16. <button onClick={() => dispatch({ type: REMOVE_TODO, payload: data.id })}>
  17. REMOVE
  18. </button>
  19. </li>
  20. )
  21. }
  22. export default ListItem

到这里我们的案例就结束了

最后

派发器思想是把所有的需要的逻辑整理到派发器中进行统一管理

派发器抛出 actionTypedispatchstate 供视图使用

使得我们编写页面更加纯粹,减少重复造轮子的工作