React Hooks useReducer 快速上手教程:从入门到实践,案例驱动
useReducer 是 React Hooks 中一个非常强大的 Hook,但对于初学者来说,它可能会显得比较抽象。本文将通过一系列由浅入深的例子,帮助你快速掌握 useReducer 的用法,并了解它在实际项目中的应用。
1. 什么是 useReducer?
useReducer 是 useState 的一个替代方案。它更适用于状态逻辑较为复杂,且包含多个子状态或者下一个 state 依赖于之前的 state 的情况。useReducer 接收一个 reducer 函数和一个初始 state 作为参数,返回当前 state 和一个 dispatch 函数。
const [state, dispatch] = useReducer(reducer, initialArg, init);
- reducer: 一个函数,接收 state 和 action,返回一个新的 state。
- initialArg: 初始 state。
- init: 一个可选的函数,用于延迟初始化 state。
- state: 当前的 state。
- dispatch: 一个函数,用于触发 state 的更新。
2. useReducer vs useState:如何选择?
useState 适合管理简单的、单一的状态。例如,一个简单的计数器,或者一个表单的输入框。
const [count, setCount] = useState(0);
useReducer 更适合管理复杂的状态,特别是当状态的更新逻辑比较复杂,或者需要依赖之前的状态时。例如,一个包含多个状态的表单,或者一个需要进行复杂计算的状态。
简单来说:
- 状态简单,更新逻辑简单: 选择
useState - 状态复杂,更新逻辑复杂: 选择
useReducer
3. useReducer 的基本使用
3.1. 计数器示例
我们先从一个最简单的计数器示例开始,来理解 useReducer 的基本用法。
import React, { useReducer } from 'react';
// 1. 定义初始 state
const initialState = { count: 0 };
// 2. 定义 reducer 函数
const reducer = (state, action) => {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
throw new Error();
}
};
function Counter() {
// 3. 使用 useReducer Hook
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
Count: {state.count}
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
</div>
);
}
export default Counter;
代码解释:
- 定义初始 state:
initialState定义了计数器的初始值为 0。 - 定义 reducer 函数:
reducer函数接收当前的state和一个action,根据action.type来更新state。这里定义了两个 action:increment和decrement,分别用于增加和减少计数器的值。 - 使用 useReducer Hook:
useReducerHook 接收reducer函数和initialState作为参数,返回当前的state和dispatch函数。 - dispatch 函数:
dispatch函数用于触发 state 的更新。它接收一个 action 对象作为参数,这个 action 对象会被传递给reducer函数。
3.2. 延迟初始化 state
useReducer 还可以接收第三个参数 init,用于延迟初始化 state。这在初始 state 的计算比较复杂时非常有用。
import React, { useReducer } from 'react';
const initialState = { count: 0 };
const reducer = (state, action) => {
switch (action.type) {
case 'increment':
return { count: state.count + action.payload };
case 'decrement':
return { count: state.count - 1 };
case 'reset':
return initialState;
default:
throw new Error();
}
};
// 初始化函数
const init = (initialState) => {
return {count: initialState.count + 10};
};
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState, init);
return (
<div>
Count: {state.count}
<button onClick={() => dispatch({ type: 'increment', payload: 5 })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
<button onClick={() => dispatch({ type: 'reset' })}>Reset</button>
</div>
);
}
export default Counter;
代码解释:
init函数接收initialState作为参数,并返回一个新的 state。在这个例子中,我们将初始的 count 值加上 10。dispatch的 action 中可以携带payload,方便传递数据给reducer。例如,在incrementaction 中,我们传递了一个payload值为 5,表示每次增加 5。
4. useReducer 的高级用法
4.1. TODO List 示例
接下来,我们用 useReducer 实现一个简单的 TODO List,来展示 useReducer 在更复杂场景下的应用。
import React, { useReducer, useState } from 'react';
// 定义 action 类型
const ACTIONS = {
ADD_TODO: 'add_todo',
TOGGLE_TODO: 'toggle_todo',
DELETE_TODO: 'delete_todo'
}
// reducer 函数
function reducer(todos, action) {
switch (action.type) {
case ACTIONS.ADD_TODO:
return [...todos, newTodo(action.payload.name)]
case ACTIONS.TOGGLE_TODO:
return todos.map(todo => {
if (todo.id === action.payload.id) {
return { ...todo, complete: !todo.complete }
}
return todo
})
case ACTIONS.DELETE_TODO:
return todos.filter(todo => todo.id !== action.payload.id)
default:
return todos
}
}
// 创建 Todo 对象
function newTodo(name) {
return { id: Date.now(), name: name, complete: false }
}
function TodoList() {
const [name, setName] = useState('')
const [todos, dispatch] = useReducer(reducer, [])
const handleSubmit = (e) => {
e.preventDefault()
dispatch({ type: ACTIONS.ADD_TODO, payload: { name: name } })
setName('')
}
return (
<div>
<form onSubmit={handleSubmit}>
<input
type="text"
value={name}
onChange={e => setName(e.target.value)}
/>
</form>
{todos.map(todo => {
return <Todo key={todo.id} todo={todo} dispatch={dispatch} />
})}
</div>
)
}
function Todo({ todo, dispatch }) {
return (
<div>
<span style={{ color: todo.complete ? '#AAA' : '#000' }}>
{todo.name}
</span>
<button onClick={() => dispatch({ type: ACTIONS.TOGGLE_TODO, payload: { id: todo.id } })}>
Toggle
</button>
<button onClick={() => dispatch({ type: ACTIONS.DELETE_TODO, payload: { id: todo.id } })}>
Delete
</button>
</div>
)
}
export default TodoList;
代码解释:
- 定义 action 类型: 使用常量来定义 action 的类型,可以避免拼写错误。
- reducer 函数:
reducer函数接收todos数组和action,根据action.type来更新todos数组。这里定义了三个 action:ADD_TODO、TOGGLE_TODO和DELETE_TODO,分别用于添加、切换完成状态和删除 TODO。 - TodoList 组件: 使用
useReducerHook 管理todos数组。同时使用useStateHook 管理输入框的值。 - Todo 组件: 展示单个 TODO,并提供切换完成状态和删除 TODO 的按钮。
4.2. 使用 Context 共享 State 和 Dispatch
当你的应用比较复杂,需要在多个组件之间共享 state 和 dispatch 时,可以使用 Context API。
import React, { useReducer, createContext, useContext } from 'react';
// 定义 action 类型
const ACTIONS = {
INCREMENT: 'increment',
DECREMENT: 'decrement',
};
// 创建 Context
const CounterContext = createContext();
// reducer 函数
const reducer = (state, action) => {
switch (action.type) {
case ACTIONS.INCREMENT:
return { count: state.count + 1 };
case ACTIONS.DECREMENT:
return { count: state.count - 1 };
default:
return state;
}
};
// 创建 Provider 组件
function CounterProvider({ children }) {
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
<CounterContext.Provider value={{ state, dispatch }}>
{children}
</CounterContext.Provider>
);
}
// 创建 Consumer Hook
function useCounter() {
return useContext(CounterContext);
}
// 使用 Counter 组件
function Counter() {
const { state, dispatch } = useCounter();
return (
<div>
Count: {state.count}
<button onClick={() => dispatch({ type: ACTIONS.INCREMENT })}>+</button>
<button onClick={() => dispatch({ type: ACTIONS.DECREMENT })}>-</button>
</div>
);
}
// 使用 AnotherCounter 组件
function AnotherCounter() {
const { state, dispatch } = useCounter();
return (
<div>
Another Counter: {state.count}
<button onClick={() => dispatch({ type: ACTIONS.INCREMENT })}>+</button>
<button onClick={() => dispatch({ type: ACTIONS.DECREMENT })}>-</button>
</div>
);
}
function App() {
return (
<CounterProvider>
<Counter />
<AnotherCounter />
</CounterProvider>
);
}
export default App;
代码解释:
- 创建 Context: 使用
createContext创建一个 Context 对象。 - 创建 Provider 组件:
CounterProvider组件使用useReducerHook 管理 state 和 dispatch,并将它们通过 Context 传递给子组件。 - 创建 Consumer Hook:
useCounterHook 使用useContextHook 消费 Context,并返回 state 和 dispatch。 - Counter 和 AnotherCounter 组件: 这两个组件都可以使用
useCounterHook 访问和修改同一个 state。
5. 总结
useReducer 是一个非常强大的 Hook,可以帮助你更好地管理复杂的状态逻辑。通过本文的例子,你应该已经掌握了 useReducer 的基本用法和高级用法。希望你能在实际项目中灵活运用 useReducer,提升你的 React 开发效率。
要点回顾:
useReducer适合管理复杂状态,尤其是当状态的更新逻辑比较复杂,或者需要依赖之前的状态时。useReducer接收一个 reducer 函数和一个初始 state 作为参数,返回当前 state 和一个 dispatch 函数。dispatch函数用于触发 state 的更新。它接收一个 action 对象作为参数,这个 action 对象会被传递给reducer函数。- 可以使用 Context API 在多个组件之间共享 state 和 dispatch。
希望这篇文章能够帮助你快速上手 useReducer。 如果你觉得这篇文章对你有帮助,欢迎点赞、评论和分享!