01. 状态管理怎么选——不是越复杂越好
React 的状态管理从简单到复杂有不同方案,关键是根据实际需要选而不是追热门:
useState——组件内部的状态。最基础,适合单个组件的简单状态。
useReducer——useState 的升级版,用 reducer 函数管理复杂状态逻辑。多个子值相互依赖时比 useState 清晰。
Context API——跨组件共享状态,不用一层层传 props。但性能上 Context 值变了所有消费者都重渲染。
Zustand——轻量外部状态库,API 极简没有 Provider 包裹、没有样板代码。适合中小项目。
Redux Toolkit——完整的状态管理方案,有 DevTools、中间件、异步处理。适合大型项目和团队协作。
一般来说:小项目 Zustand 够用,中项目 Context + useReducer 够用,大项目考虑 Redux Toolkit。不要上来就 Redux。
02. useState 与 useReducer——局”,状态管理
useState 是最简单的状态 hook:const [value, setValue] = useState(initialValue)。setValue 触发重渲染。
useReducer 适用场景:状态逻辑复杂(多个状态相互依赖)、下一个状态依赖上一个状态。写法:const [state, dispatch] = useReducer(reducer, initialState)。reducer 是纯函数 (state, action) => newState。
什么时候该用 useReducer 而不是 useState:
1. 一个操作要同时改多个状态(如表单提交后清空成功消息和表单值)
2. 下一个状态需要基于上一个状态计算(如计数器、分步表单)
3. 状态更新逻辑需要被测试(reducer 是纯函数,单元测试很容易写)
javascript
// useState
const [count, setCount] = useState(0);
// 基于前一个值更新
setCount(prev => prev + 1);
// useReducer
const reducer = (state, action) => {
switch (action.type) {
case 'increment':
return { ...state, count: state.count + 1 };
case 'decrement':
return { ...state, count: state.count - 1 };
case 'setName':
return { ...state, name: action.payload };
default:
return state;
}
};
const [state, dispatch] = useReducer(reducer, {
count: 0,
name: ''
});03. Context API——跨组件共享数据
Context 解决的是 props drilling 问题——父组件要给很深的子组件传数据,中间每一层都要转发 props。Context 让你在组件树里创建一个数据隧道。
三步:createContext 建一个 context;在父组件用 Provider 提供数据;在子组件用 useContext 读取数据。
Context 的局限:Provider 的值变了,所有用到这个 Context 的组件都会重新渲染——即使它们只用了 Context 里某个没变的值。解决方法:拆分 Context(把不同关注点的状态放不同 Context);或用 useMemo 记忆传给 Context 的值。
Context 不等于状态管理——它只是依赖注入机制。实际的状态管理(更新、派生)还需要 useState 或外部库。Context 负责传递,状态管理库负责存储和更新。
jsx
// 创建 Context
const ThemeContext = createContext('light');
// 提供数据
function App() {
const [theme, setTheme] = useState('light');
const value = useMemo(() => ({ theme, setTheme }), [theme]);
return (
<ThemeContext.Provider value={value}>
<MainContent />
</ThemeContext.Provider>
);
}
// 消费数据
function Button() {
const { theme, setTheme } = useContext(ThemeContext);
return (
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
Current: {theme}
</button>
);
}Context 的值每次渲染都是新对象的话(即使内容没变),消费者也会全部重渲染。用 useMemo 缓存 context value 对象。
04. Zustand——轻量状态库入门
Zustand 是当下 React 社区最受青睐的轻量状态库之一。它最吸引人的是没有 Provider 包裹、没有样板代码、API 就是调用函数。
核心概念:create 函数创建一个 store,里面定义了状态和修改状态的方法。组件里直接调用 useStore(selector) 拿需要的部分。
selector 的作用——只选择你需要的状态。Zustand 只在 selector 返回的值变化时才重渲染组件,避免了 Context 的全量渲染问题。
异步操作也很直接——store 的方法可以是 async 函数的。直接 async set 就行,不需要中间件。
对比 Redux:Zustand 不需要 reducer、dispatch、action 这些概念。定义状态和方法,直接在组件里用。代码少很多。
jsx
// Zustand store
import { create } from 'zustand';
const useCounterStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
reset: () => set({ count: 0 }),
}));
// 组件里使用
function Counter() {
const count = useCounterStore((s) => s.count);
const increment = useCounterStore((s) => s.increment);
return (
<div>
<span>{count}</span>
<button onClick={increment}>+</button>
</div>
);
}Zustand 的 selector 是默认浅比较的——只有你选出来的值变了才重渲染。不需要像 Context 那样手动 useMemo。
05. 什么时候需要全局状态、什么时候不需要
不是所有状态都要拉成全局状态。很多状态放在组件内部就够了:
组件内部状态——只有这一个组件需要关心的数据。比如表单输入值、展开/收起状态、弹窗的显示。用 useState。
组件树共享状态——父子组件之间传 props 就行。如果只传 1~2 层,不要因为怕 prop drilling 就上全局状态。
全局状态——多个完全无关的组件需要共享的数据。比如用户登录信息、主题设置、购物车。这种才放到全局状态里。
服务器状态——API 返回的数据(用户列表、订单详情)。不要手动存到 Redux/Zustand 里自己管理缓存。用 React Query 或 SWR——它们自带缓存、重新获取、乐观更新。
表单状态——表单的复杂状态用 React Hook Form 比手动 useReducer 好得多。它是表单领域的专项工具。
jsx
// 服务器状态——用 React Query,不要手动管理
import { useQuery } from '@tanstack/react-query';
function UserList() {
const { data, isLoading } = useQuery({
queryKey: ['users'],
queryFn: () => fetch('/api/users').then(r => r.json()),
});
if (isLoading) return <div>Loading...</div>;
return <ul>{data.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}React Query / SWR 这种服务器状态库让你不用操心缓存、loading、refetch 这些事。它们是全局状态库里很大一块内容的最佳替代品。
知识测验
第 1/5 题正确 0
useState 和 useReducer 什么时候该用后者?
下一节
下一节 路由管理