前言
这次我们使用 React Hooks
来实现派发器思想
其实 React Hooks
对派发器思想的支持非常友好
因为他自带的 useReducer
的使用方式刚好符合派发器思想这一特点
所以说是 React Hooks
实现派发器思想
不如说是 useReducer
使用教程
这里我们依然使用万能的 TodoList
来做例子
项目初始换
这里我们使用 Vite
当作 dev
工具来展示
在终端中输入 yarn create vite
来初始化我们的项目
框架我们选择 React
后面使用 JS TS 都行看个人选择
我这边就先选择 JS 来做展示
然后进入到目录,安装 node_modules
修改目录
首先清理文件
把不必要的文件删除就可以
最后保留的像我这样即可
修改 App.jsx
function App() {
return (
<div>
</div>
)
}
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 - 变量
- TodoList - 文件夹
都创建完成之后大概就是这样子
components目录文件初始化
JSX 文件初始化
首先我们先把大框架给搭好再写内容
把每个 components 下的 JSX 文件像这样写好
这里复制粘贴即可
就算不改函数名问题也不大,改了是可以更好的区分内容的
function Index /* 对应文件夹的名字 */() {
return (
<div>
</div>
)
}
export default Index
store 文件初始化
state.js
这个文件只是用来保存变量的
所以不用太复杂
export default {
}
actions.js
export default function (state) {
return {
}
}
actionTypes.js 暂时先空着
reducer.js
import initialState from "./state"
import action from "./action"
import { } from "./actionType"
const { useReducer } = React
function reducer(state, { type, payload }) {
const { } = action(state)
switch (type) {
default:
return state;
}
}
export default function () {
return useReducer(reducer, initialState)
}
index.js
import todoReducer from "./reducer"
export { } from "./actionType"
分析需求
一个基本的 TodoList
需要带有三种功能
- 添加 Todo
- 删除 Todo
- 切换 Todo 状态
这样我们就得到了三个基本需求
在深入点的分析 Todo 需要的东西
- Todo
- 提供一个 content 来表示需要做什么事情
- 提供一个 id 来做唯一表示,因为 content 可能有会重复
- 提供一个 completed 状态来表示是否完成
ok 到此为止,我们已经基本确定一个 TodoList 需要的东西了
一个的 TodoItem 对象需要的东西
{
id: 0, // 我们用时间戳做唯一表示
content: '', // 表示需要做的事情的内容
completed: false, // 表示此 Todo 是否已经完成
}
App.jsx 完成
import TodoList from "./components/TodoList"
// 只需要引入组件即可 所有的逻辑都在组件里完成
function App() {
return (
<div>
<TodoList />
</div>
)
}
export default App
TodoList > index.jsx 视图与部分逻辑完成
import Form from "./Form"
import List from "./List"
// 这里什么逻辑也没有只是先引入组件
function TodoList() {
return (
<div>
<Form />
<List />
</div>
)
}
export default TodoList
Form.jsx 视图与部分逻辑完成
首先我们肯定要先展示视图
在 src/App.jsx 引入 TodoList 并展示
import TodoList from "./components/TodoList"
function App() {
return (
<div>
<TodoList />
</div>
)
}
export default App
在 src/components/index.jsx 引入 Form.jsx 并展示
import Form from "./Form"
function Index() {
return (
<div>
<Form />
</div>
)
}
export default Index
编写 Form.jsx 视图
function Form() {
return (
<div>
{/* content 输入框 */}
<input
type={'text'}
placeholder={'please input something...'}
/>
{/* 触发 添加Todo 事件的按钮 */}
<button>ADD</button>
</div>
)
}
export default Form
编写 Form.jsx 部分逻辑
import { useState } from "react"
function Form() {
// Todo.content
const [content, setContent] = useState('')
const addTodo = () => {
if (content.length === 0) {
alert('请输入需要做的内容')
return
}
const todo = {
content,
id: Date.now(), // 用当前时间戳来做 ID
completed: false, // 默认为未完成状态
}
}
return (
<div>
{/* content 输入框 */}
<input
type={'text'}
placeholder={'please input something...'}
value={content}
onChange={event => setContent(event.target.value)} // 更改 content
/>
{/* 触发 添加Todo 事件的按钮 */}
<button>ADD</button>
</div>
)
}
export default Form
List.jsx
import ListItem from "./ListItem"
function List({
// 先定义好需要从父组件获取的数据
todoList = 0,
addCount = 0,
removeCount = [],
}) {
return (
<div>
<p>已添加过{addCount}条</p>
<p>已删除过{removeCount}条</p>
<ul>
{
todoList.map(item => (
<ListItem
data={item}
key={item.id}
/>
))
}
</ul>
</div>
)
}
export default List
ListItem.jsx 视图与部分逻辑完成
也跟上面同样的道理
先定义好数据
function ListItem({ data }) {
return (
<li>
<input
type="checkbox"
checked={data.completed}
/>
<span style={{ textDecoration: data.completed && 'line-through' }}>
{data.content}
</span>
<button onClick={() => dispatch({ type: REMOVE_TODO, payload: data.id })}>
REMOVE
</button>
</li>
)
}
export default ListItem
编写 store 的数据与逻辑
state.js
这只是个存放数据的地方
export default {
todoList: [], // 当前的 Todo 列表
addCount: 0, // 一共添加过多少条数据
removeCount: 0, // 一共删除过多少条数据
}
actionTypes.js
export const
ADD_TODO = 'ADD_TODO', // 对应添加 Todo 的操作
REMOVE_TODO = 'REMOVE_TODO', // 对应删除 Todo 的操作
TOGGLE_TODO = 'TOGGLE_TODO' // 对应切换 Todo 状态的操作
action.js
export default function (
state // 接收 state 的数据
) {
// 添加 Todo 的事件
const onAddTodo = (todo) => {
return {
...state,
todoList: [...state.todoList, todo],
addCount: state.addCount + 1
}
}
// 切换 Todo 状态的事件
const onToggleTodo = (id) => {
const todoList = state.todoList.map(item => (
item.id === id && (item.completed = !item.completed),
item
))
return {
...state,
todoList
}
}
// 删除 Todo 的事件
const onRemoveTodo = (id) => {
return {
...state,
todoList: state.todoList.filter(item => item.id !== id),
removeCount: state.removeCount + 1
}
}
return {
onAddTodo,
onToggleTodo,
onRemoveTodo,
}
}
归纳到 reducer.js 中进行集中派发
import initialState from "./state"
import action from "./action"
import {
ADD_TODO,
TOGGLE_TODO,
REMOVE_TODO,
} from "./actionType"
// 这里我们使用 React 的 useReducer 进行派发
const { useReducer } = React
function reducer(
state, // 数据
{
type, // 触发事件的 type
payload, // 传入的参数
}
) {
const {
onAddTodo,
onToggleTodo,
onRemoveTodo,
} = action(state)
switch (type) {
case ADD_TODO:
return onAddTodo(payload);
case TOGGLE_TODO:
return onToggleTodo(payload);
case REMOVE_TODO:
return onRemoveTodo(payload);
default:
return state;
}
}
export default function () {
return useReducer(reducer, initialState)
}
最后通过 index.js 进行集中导出
import todoReducer from "./reducer"
import {
ADD_TODO,
REMOVE_TODO,
TOGGLE_TODO,
} from "./actionTypes"
export {
ADD_TODO,
REMOVE_TODO,
TOGGLE_TODO,
todoReducer,
}
现在我们在 index.jsx 去使用他
import FormComp from "./Form"
import ListComp from "./List"
import { todoReducer } from "./store"
import { createContext } from 'react'
/**
* react 的上下文函数
* 可以使所有包裹的子组件通过 useContext 去使用里面的数据或者函数
*/
export const ListContext = createContext({})
function TodoList() {
const [
{
todoList,
addCount,
removeCount,
},
dispatch, // 派发器函数
] = todoReducer()
return (
<div>
<FormComp dispatch={dispatch} />
<ListContext.Provider
value={{ dispatch }}
>
<ListComp
addCount={addCount}
removeCount={removeCount}
todoList={todoList}
/>
</ListContext.Provider>
</div>
)
}
export default TodoList
Form.jsx 的逻辑编写
import { useState } from "react"
import { ADD_TODO } from "./store"
function Form({ dispatch }) {
const [content, setContent] = useState('')
const addTodo = () => {
if (content.length === 0) {
alert('请输入需要做的内容')
return
}
const todo = {
content,
id: Date.now(),
completed: false,
}
// 发送 ADD_TODO指令和payload参数 到派发器中进行处理
dispatch({
type: ADD_TODO,
payload: todo,
})
}
return (
<div>
<input
type={'text'}
placeholder={'please input something...'}
value={content}
onChange={event => setContent(event.target.value)}
/>
<button onClick={addTodo}>ADD</button>
</div >
)
}
export default Form
ListItem.jsx 逻辑编写
import { ListContext } from "./index"
import { REMOVE_TODO, TOGGLE_TODO } from "./store"
import { useContext } from "react"
function ListItem({ data }) {
const { dispatch } = useContext(ListContext)
return (
<li>
<input
type="checkbox"
checked={data.completed}
onChange={() => dispatch({ type: TOGGLE_TODO, payload: data.id })}
/>
<span style={{ textDecoration: data.completed && 'line-through' }}>
{data.content}
</span>
<button onClick={() => dispatch({ type: REMOVE_TODO, payload: data.id })}>
REMOVE
</button>
</li>
)
}
export default ListItem
到这里我们的案例就结束了
最后
派发器思想是把所有的需要的逻辑整理到派发器中进行统一管理
派发器抛出 actionType
、dispatch
、state
供视图使用
使得我们编写页面更加纯粹,减少重复造轮子的工作