React中的useReducer
我们在写逻辑的时候,通常是一个业务一个方法,这样使得代码的逻辑看上去会很复杂,所以在React中提出了useReducer来进行方法的结构,目的时使用这种方式实现方法的复用,使得项目的扩展性更强。
我们通过一个例子来展示useReducer的好处
在React中常规todoList的写法
构思组件
根据分析,我们可以把组件分成两个部分,头部输入框以及主要数据展示区
于是我们可以构建一下两个组件对其业务进行分析以及编写
index.jsx为两个子组件的父组件,用于统一两个组件并一同注册与App.js中
完成组件业务功能
import React, { useState } from 'react'
function TodoForm(props) {
const [todoText, setTodoText] = useState('')
const { onAddTodo } = props
const addTodo = () => {
onAddTodo({
id: new Date().getTime(),
content: todoText,
completed: false
})
setTodoText('')
}
return (
<div>
<input
type="text"
placeholder="Please type something"
value={todoText}
onChange={(e) => setTodoText(e.target.value)}
/>
<button onClick={addTodo}>ADD ITEM</button>
</div>
)
}
export default TodoForm
function TodoList(props) {
const { todoList, onToggleTodo, onRemove } = props
return (
<ul>
{todoList &&
todoList.map((item) => (
<li key={item.id}>
<input
type="checkbox"
checked={item.completed}
onChange={() => onToggleTodo(item.id)}
/>
<span
style={{
textDecoration: item.completed ? 'line-through' : 'none'
}}
>
{item.content}
</span>
<button
onClick={() => {
onRemove(item.id)
}}
>
remove
</button>
</li>
))}
</ul>
)
}
export default TodoList
import React, { useCallback, useState } from 'react'
import TodoForm from './Form'
import TodoList from './List'
function TodoApp() {
const [todoList, setTodoList] = useState([])
const addTodo = useCallback((todo) => {
setTodoList((todoList) => [...todoList, todo])
}, [])
const toggleTodo = useCallback((id) => {
setTodoList((todoList) =>
todoList.map((item) => {
if (item.id === id) {
item.completed = !item.completed
}
return item
})
)
}, [])
const removeTodo = useCallback((id) => {
setTodoList((todoList) => todoList.filter((item) => item.id !== id))
}, [])
return (
<div>
<TodoForm onAddTodo={addTodo}></TodoForm>
<TodoList
todoList={todoList}
onToggleTodo={toggleTodo}
onRemove={removeTodo}
></TodoList>
</div>
)
}
export default TodoApp
从上面的方法我们可以看出,一旦方法多了,就会出现业务逻辑不够清晰的情况,方法混乱,后期维护会越来越困难
于是React的开发人员也想到了这一点,设计出了useReducer Hook来解决这一问题
下面我们看看使用useReducer Hook 如何解决这类问题
我们的两个组件无需做任何改变,重点改变在index.jsx中
import React, { useReducer } from 'react'
import TodoForm from './Form'
import TodoList from './List'
import actionTypes from './store/actionTypes'
import { todoReducer } from './store/reducer'
import { initialTodoList } from './store/state'
function TodoApp() {
const [todoList, todoDispatch] = useReducer(todoReducer, initialTodoList)
return (
<div>
<TodoForm
onAddTodo={(todo) =>
todoDispatch({ type: actionTypes.ADD_LIST, payLoad: todo })
}
></TodoForm>
<TodoList
todoList={todoList}
onToggleTodo={(id) =>
todoDispatch({ type: actionTypes.TOGGLE_LIST, payLoad: id })
}
onRemove={(id) => {
todoDispatch({ type: actionTypes.REMOVE_LIST, payLoad: id })
}}
></TodoList>
</div>
)
}
export default TodoApp
在todolist文件夹中创建一个文件夹store来完成我们对应的业务拆解
了解React中useReducer的应该都知道 useReducer的使用要求我们传入一个回调函数以及数据的初始值
我们朝着这方面开始设计程序
在store文件夹中创建state.js用于集中导出我们需要导出的我们的数据初始值
const initialTodoList = []
export { initialTodoList }
在store文件夹中创建actionTypes.js创建文件主要目的是为了后期的代码维护,并且可以更好的提交编码的效率
const actionTypes = {
ADD_LIST: 'ADD_TODO',
TOGGLE_LIST: 'TOGGLE_LIST',
REMOVE_LIST: 'REMOVE_LIST'
}
export default actionTypes
在store文件中创建reducer.js,创建这个文件主要是一个type分发器,主要逻辑是根据type的不同完成实现不同个方法
import actionTypes from './actionTypes'
import { addTodo, toggleTodo, removeTodo } from './todoFunc'
function todoReducer(todoList, action) {
const { type, payLoad } = action
switch (type) {
case actionTypes.ADD_LIST:
return addTodo(todoList, payLoad)
case actionTypes.TOGGLE_LIST:
return toggleTodo(todoList, payLoad)
case actionTypes.REMOVE_LIST:
return removeTodo(todoList, payLoad)
default:
break
}
}
export { todoReducer }
在store文件中todoFunc.js,主要用于创建我们现在需要的具体方法
function addTodo(todoList, payLoad) {
return [...todoList, payLoad]
}
function toggleTodo(todoList, payLoad) {
return todoList.map((item) => {
if (item.id === payLoad) {
item.completed = !item.completed
}
return item
})
}
function removeTodo(todoList, payLoad) {
return todoList.filter((item) => item.id != payLoad)
}
export { addTodo, toggleTodo, removeTodo }
在经过这样书写之后我们还是可以完成响应的业务,代码看上去复杂了,但是我们实则已经将代码进行了解耦,是代码的可维护性更强,是代码更加健壮,易于扩展
有了React的基础,让我有了一种思考,是否可以把这种设计思想用在我们的Vue3中呢。答案是可以的,说做就做,下面我来展示在Vue3完成类似于React中useReducer的Hook
使用Vue3实现类似于React中useReducer的Hooks
了解到在React中的userReducer是返回一个数组,数组中可以结构出我们需要数据的响应式数据,以及一个分发器Dispatch
那么我们在src目录下创建一个文件夹Hooks用来写我们自定的Hooks
创建useReducer.js
import { ref } from 'vue'
function useReducer(reducer, initialState) {
const state = ref(initialState)
const action = {}
function dispatch({ type, payLoad }) {
action.type = type
action.payLoad = payLoad
reducer(state, action)
}
return [state, dispatch]
}
export default useReducer
现在我们有了类似于React中useReducer的Hook
那么我们就可以模仿前面的代码进行代码编写
在Components文件夹下创建todoList,用来编写我们的TodoList业务逻辑
下面直接上代码
<template>
<div>
<input type="text" placeholder="please type somthing" v-model="todoText" />
<button @click="addTodo">ADD ITEM</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
const emits = defineEmits(['onAddTodo'])
const todoText = ref('')
const addTodo = () => {
if (todoText.value !== '') {
emits('onAddTodo', {
id: new Date().getTime(),
content: todoText.value,
completed: false
})
} else {
alert('输入不能为空')
}
todoText.value = ''
}
</script>
<style></style>
<template>
<ul>
<li v-for="item of props.todoList" :key="item.id">
<input
type="checkbox"
:checked="item.completed"
@click="toggleTodo(item.id)"
/>
<span
:style="{ textDecoration: item.completed ? 'line-through' : 'none' }"
>{{ item.content }}</span
>
<button @click="removeTodo(item.id)">REMOVE</button>
</li>
</ul>
</template>
<script setup>
const props = defineProps({
todoList: {
type: Array,
default() {
return []
}
}
})
const emits = defineEmits(['onToggleTodo', 'onRemoveTodo'])
const toggleTodo = (id) => {
emits('onToggleTodo', id)
}
const removeTodo = (id) => {
emits('onRemoveTodo', id)
}
</script>
<style></style>
<template>
<div>
<todo-form
@onAddTodo="
(todo) => todoDispatch({ type: actionTypes.ADD_LIST, payLoad: todo })
"
/>
<todo-list
:todoList="todoList"
@onToggleTodo="
(id) => todoDispatch({ type: actionTypes.TOGGLE_LIST, payLoad: id })
"
@onRemoveTodo="
(id) => todoDispatch({ type: actionTypes.REMOVE_LIST, payLoad: id })
"
/>
</div>
</template>
<script setup>
import TodoForm from './Form.vue'
import TodoList from './List.vue'
import { todoReducer, actionTypes } from './store'
import { useReducer } from '../../hooks'
const [todoList, todoDispatch] = useReducer(todoReducer, [])
</script>
<style></style>
同样我们还是需要想React项目中一样的业务逻辑代码
这里我就直接上代码了
const actionTypes = {
ADD_LIST: 'ADD_TODO',
TOGGLE_LIST: 'TOGGLE_LIST',
REMOVE_LIST: 'REMOVE_LIST'
}
export default actionTypes
import actionTypes from './actionTypes'
import { todoReducer } from './reducer'
export { actionTypes, todoReducer }
import actionTypes from './actionTypes'
import { addTodo, toggleTodo, removeTodo } from './todoFunc'
function todoReducer(state, action) {
const { type, payLoad } = action
switch (type) {
case actionTypes.ADD_LIST:
return addTodo(state, payLoad)
case actionTypes.TOGGLE_LIST:
return toggleTodo(state, payLoad)
case actionTypes.REMOVE_LIST:
return removeTodo(state, payLoad)
default:
break
}
}
export { todoReducer }
function addTodo(state, payLoad) {
state.value.push(payLoad)
}
function toggleTodo(state, payLoad) {
state.value = state.value.map((item) => {
if (item.id === payLoad) {
item.completed = !item.completed
}
return item
})
}
function removeTodo(state, payLoad) {
state.value = state.value.filter((item) => item.id !== payLoad)
}
export { addTodo, toggleTodo, removeTodo }
总结
经过上面的论述,我们已经基本实现了React中的设计思路搬迁,在今后的学习过程中我还会继续分享我的想法!