useReducer
是 React 中用于管理复杂状态逻辑的 Hook,尤其适合处理包含多个子值、依赖前一个状态或需要统一管理的场景。
基础用法
要使用 useReducer,你需要从 React 库中导入它,并在组件的顶层调用它。useReducer 接受三个参数:reducer 函数、初始状态和一个可选的初始化函数。它返回一个包含当前状态和 dispatch 函数的数组。
const [state, dispatch] = useReducer(reducer, initialArg, init?)
useReducer
接收三个参数:
reducer 函数:指定如何更新状态的还原函数,它必须是纯函数,以 state 和 dispatch 为参数,并返回下一个状态。
初始状态:初始状态的计算值。
(可选的)初始化参数:用于返回初始状态。如果未指定,初始状态将设置为 initialArg;如果有指定,初始状态将被设置为调用init(initialArg)
的结果。
useReducer
返回两个参数:
当前的状态:当前状态。在第一次渲染时,它会被设置为init(initialArg)
或 initialArg(如果没有 init 的情况下)。
dispatch:调度函数,用于调用 reducer 函数,以更新状态并触发重新渲染。
import React, { useReducer } from 'react';
const initialState = { count: 0 };
//确保 reducer 是纯函数,不直接修改 state,始终返回新对象。
function reducer(state, action) {
//使用 TypeScript 或约定好的字符串常量定义 action.type,避免拼写错误。
switch (action.type) {
case 'INCREMENT':
return { count: state.count + 1 };
case 'DECREMENT':
return { count: state.count - 1 };
default:
return state;
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({ type: 'DECREMENT' })}>-</button>
<button onClick={() => dispatch({ type: 'INCREMENT' })}>+</button>
</>
);
}
异步使用
在 useEffect 中 Dispatch:将异步逻辑放在 useEffect
或事件处理函数中,完成后触发 dispatch
。
const fetchData = async () => {
dispatch({ type: 'FETCH_START' });
try {
const data = await api.get();
dispatch({ type: 'FETCH_SUCCESS', payload: data });
} catch (error) {
dispatch({ type: 'FETCH_ERROR', payload: error });
}
};
与 Context API 结合
const AppContext = React.createContext();
const AppProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<AppContext.Provider value={{ state, dispatch }}>
{children}
</AppContext.Provider>
);
};
//子组件中获取reducer值
function Child() {
const { state, dispatch } = useContext(AppContext);
return (
<>
<button onClick={() => dispatch({ type: 'DECREMENT' })}>-</button>
<span>{state.count}</span>
<button onClick={() => dispatch({ type: 'INCREMENT' })}>+</button>
</>
);
}
初始状态延迟初始化
const initState = (initialCount) => ({ count: initialCount });
const [state, dispatch] = useReducer(reducer, initialCount, initState);
useReducer
还有第三个参数init
,那么它的作用是什么?它也是为了性能优化而来。
有一个场景,计数器的值保存在localStorage
里面,进入页面的时候,我们希望从localStorage
中读取值来作为useReducer
初值,如果没有init
,我们可以这样做:
function getInit() {
const savedCount = localStorage.getItem("count");
return savedCount ? Number(savedCount) : 0;
}
function counterReducer(state, action) {
switch (action.type) {
case "INCREMENT":
return { count: state.count + 1 };
case "DECREMENT":
return { count: state.count - 1 };
default:
return state;
}
}
function Counter() {
const [state, dispatch] = useReducer(counterReducer, getInit());
// 使用useEffect来监听状态的变化,并将其保存到localStorage
useEffect(() => {
localStorage.setItem("count", state.count);
}, [state.count]);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({ type: "INCREMENT" })}>+1</button>
<button onClick={() => dispatch({ type: "DECREMENT" })}>-1</button>
</>
);
}
我们直接调用getInit
函数作为useReducer
的第二个参数,从而得到初始状态。当React初始化这个组件时,它会执行这个函数并使用其返回值作为初始状态。
如果在第三个参数里进行初始化,代码是这样写:
function init(initialValue) {
// 尝试从localStorage中读取值
const savedCount = localStorage.getItem("count");
// 如果有值并且可以被解析为数字,则返回它,否则返回initialValue
return { count: savedCount ? Number(savedCount) : initialValue };
}
function counterReducer(state, action) {
switch (action.type) {
case "INCREMENT":
return { count: state.count + 1 };
case "DECREMENT":
return { count: state.count - 1 };
default:
return state;
}
}
function Counter() {
const [state, dispatch] = useReducer(counterReducer, 0, init);
// 使用useEffect来监听状态的变化,并将其保存到localStorage
useEffect(() => {
localStorage.setItem("count", state.count);
}, [state.count]);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({ type: "INCREMENT" })}>+1</button>
<button onClick={() => dispatch({ type: "DECREMENT" })}>-1</button>
</>
);
}
最终功能一样的,但它们区别很大:
1. 执行时机
直接调用函数作为第二个参数:这个函数会在每次组件渲染时执行。使用init
函数:init
函数只在组件初次渲染时执行一次。
2.访问到的数据:
直接调用函数作为第二个参数:这个函数只能访问到定义它时的作用域内的数据。使用init
函数:由于init
函数接受initialArg
作为参数,这使得init
函数具有更大的灵活性,能够基于传入的参数进行计算。
3.性能:
直接调用函数作为第二个参数:这个函数执行了复杂计算操作,那么在每次组件渲染时都会执行,可能会导致性能问题。使用init
函数:由于它只在组件的初始化阶段执行一次,所以对于那些计算复杂的初始化操作,使用init
函数可能会更为高效。
useState
或 useReducer
简单状态:用 useState
。
复杂状态流:用 useReducer
。
useReducer
与 Redux 的差异
虽然useReducer
和 Redux 都采用了 action 和 reducer 的模式来处理状态,但它们在实现和使用上有几个主要的区别:
范围:useReducer
通常在组件或小型应用中使用,而Redux被设计为大型应用的全局状态管理工具。
中间件和扩展:Redux支持中间件,这允许开发者插入自定义逻辑,例如日志、异步操作等。而useReducer
本身不直接支持,但我们可以模拟中间件的效果。
复杂性:对于简单的状态管理,useReducer
通常更简单和直接。但当涉及到复杂的状态逻辑和中间件时,Redux可能更具优势。