前言
在讲useReducer之前需要介绍一下useState这个hooks函数。useState用于数据管理,响应式数据管理,允许我们在函数组件中存储状态。
随着应用逐渐复杂,我们经常发现useState
在管理复杂的状态逻辑时显得有些力不从心。这时,React为我们提供的另一个更为强大的hook——useReducer
——可以帮助我们优雅地处理复杂状态。
useReducer
允许我们使用 action 和 reducer 的方式来组织复杂的状态逻辑,使其变得更加清晰和模块化,弥补了useState
的局限性。
今天让我们用useReducer
+useContext
来实现一个todosList功能。
了解一下什么是useReducer
?
useReducer
与useState
相似,useReducer
也是 React 的 Hook,而且也只能放在组件最顶层使用。与前者不同的地方在于,它是通过 action 来更新状态的,使状态更新逻辑更具可读性。
useReducer
接受三个参数:
reducer
:用于更新 state 的纯函数。参数为 state 和 action,返回值是更新后的 state。state 与 action 可以是任意合法值。initialArg
:用于初始化 state 的任意值。初始值的计算逻辑取决于接下来的init
参数。- 可选参数
init
:用于计算初始值的函数。如果存在,使用init(initialArg)
的执行结果作为初始值,否则使用initialArg
。
什么是纯函数?
纯函数(Pure Function)是函数式编程中的一个核心概念,它指的是那些在同样的输入下总是产生相同输出的函数,并且不会产生任何副作用。具体来说,纯函数具有以下两个主要特性:
- 确定性:给定相同的输入参数,纯函数总是返回相同的结果。这意味着纯函数的输出仅取决于它的输入参数,不受外部状态(比如全局变量、数据库查询或网络请求等)的影响。
- 无副作用:纯函数不会修改任何外部状态或数据,也不会进行任何形式的I/O操作,例如文件系统操作、网络请求或打印到控制台等。它们仅仅根据输入参数计算并返回结果。
useReducer
返回两个参数:
useReducer
返回一个由两个值组成的数组:
- 当前的 state。初次渲染时,它是
init(initialArg)
或initialArg
(如果没有init
函数)。 dispatch
函数。用于更新 state 并触发组件的重新渲染。
const [state, dispatch] = useReducer(reducer, initialArg, init?)
当你使用 useReducer
时,useReducer
返回一个数组,第一个元素是当前的状态 (state
),第二个元素是一个可以用来触发状态更新的 dispatch
函数。通过解构赋值,我们可以方便地将这两个值分别赋给 state
和 dispatch
变量。
实现todoList
1.创建全局上下文
export const TodoContext = createContext(null);
2.创建自定义hooks函数useTodos
,其主要作用是封装待办事项(todos)的状态管理和操作逻辑,方便在不同组件中复用。
const initialTodos =[
{
id:1,
text:'学习React',
done:false
}
]
export function useTodos(initial=initialTodos) {
const [todos,dispatch] = useReducer(todoReducer,initial)
const addTodo = text => dispatch({type: 'ADD_TODO',text})
const toggleTodo =(id)=> dispatch({type: 'TOGGLE_TODO',id})
const removeTodo =(id)=> dispatch({type: 'REMOVE_TODO',id})
return {
todos,
addTodo,
toggleTodo,
removeTodo
}
}
3.在App.jsx
中,用创建好的上下文包裹子组件,这样就不需要传递数据了,value
属性指定了要共享的数据,这里共享的是 todosHook
对象。todosHook
包含当前的待办事项列表 todos 和三个操作函数 addTodo 、 toggleTodo 、 removeTodo 。
function App() {
const todosHook=useTodos();
return (
<TodoContext.Provider value={todosHook}>
<h1>Todo App</h1>
<AddTodo />
<TodoList />
</TodoContext.Provider>
)
}
4.创建一个纯函数,用来出来todoReducer
用于根据不同的 action 来更新待办事项的状态。reducer 接收两个参数:
- state :当前的待办事项状态,通常是一个数组。
- action :一个对象,包含 type 属性(表示操作类型)和其他必要的数据。
function todoReducer(state,action){
switch(action.type){
case 'ADD_TODO':
return [...state,{
id:Date.now(),
text:action.text,
done:false
}]
case 'TOGGLE_TODO':
return state.map(todo =>
todo.id === action.id ?{...todo,done:!todo.done}:todo
);
case 'REMOVE_TODO':
return state.filter(todo => todo.id !== action.id);
default:
return state;
}
}
5.创建一个自定义hooksuseTodoContext
,使用全局上下文.创建这么多自定义hooks函数,就是为了让组件内部更加干净,更好管理。
export function useTodoContext(){
return useContext(TodoContext);
}
6.完成添加todo的功能
const AddTodo =()=>{
const [text,setText]=useState('');
const { addTodo }=useTodoContext();// 跨层级
const handleSubmit=(e)=>{
e.preventDefault(); // 阻止表单默认行为
if(text.trim()){ // 去空格
addTodo(text.trim()); // 调用addTodo方法添加任务
setText(''); // 清空输入框
}
}
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={text}
onChange={(e)=>{
setText(e.target.value)
}}
/>
<button type="submit">Add</button>
</form>
)
}
7.完成展示todoslist的功能
const TodoList = () => {
const {
todos,
toggleTodo,
removeTodo,
} = useTodoContext();
return (
<ul>
{
todos.map((todo)=>(
<li key={todo.id}>
<span
onClick={()=>toggleTodo(todo.id)}
style={{textDecoration:todo.done ? 'line-through':'none'}}
>
{todo.text}
{/* {localStorage.getItem('text')} */}
</span>
<button onClick={()=>removeTodo(todo.id)}>remove</button>
</li>
))
}
</ul>
)
}
总结
- useReducer 核心 :包含响应式状态管理、使用纯函数 reducer 规定状态改变规则、初始值 initValue 以及通过 dispatch 派发 action 对象。
- useContext 作用 :用于跨层次共享状态,借助 createContext 创建上下文, Context.Provider 提供状态, useContext 消费状态。
- 组合使用 :将 useContext 和 useReducer 结合,可实现跨层级的全局状态管理,应用场景包括主题、登录状态、待办事项等。
- 自定义 Hook :介绍了组件渲染与 Hook 状态管理的关系,以及 hook 分别与 useContext 、 useReducer 组合使用在全局应用级别状态管理中的作用。