我开始写这个系列时,以为"状态管理"就是选个库(Redux 还是 Zustand)的问题。写完 9 篇,我才意识到:状态管理是一个思维方式,而非技术选型。
这篇文章不是技术总结,而是我的认知转变记录。
系列的初衷
最开始,我只是想整理一些不太熟悉的知识点:
encodeURIComponent到底什么时候用?- 登录后怎么跳转回原页面?
- 多标签页的登录状态如何同步?
我以为这些都是"小问题",查查文档就能解决。但真正梳理下来,发现背后涉及的思考远比想象的深。
AI 在这个过程中帮了很多——快速生成代码、解释概念、提供多种方案。但同时,AI 也暴露了我的很多盲区:
- 我对很多"常识"的理解其实很浅
- 我习惯性地追求"标准答案",但技术选型往往没有标准答案
- 我缺少系统的思维框架
最大的收获不是"学会了什么工具",而是"学会了如何思考"。
接下来,我想分享写完这个系列后的 5 个核心认知转变。
认知 1:服务端数据不是"状态",是"远程缓存"
写文章前的认知
我以为服务端数据也是"状态",用 useState 管理就够了:
// 环境:React 18+
// 场景:获取用户信息
function UserProfile() {
const [user, setUser] = useState(null);
useEffect(() => {
fetch('/api/user')
.then(res => res.json())
.then(setUser);
}, []);
return <div>{user?.name}</div>;
}
当时觉得:这不就是"状态管理"吗?有什么问题?
写第 5 篇时的顿悟
在写《用 useState 管理服务端数据?不如试试 React Query 来“避坑”》时,我突然意识到:服务端数据有特殊性。
客户端的 user 变量,只是服务器数据的缓存副本。真正的"真相源"(source of truth)在服务器。
这意味着:
- 过期问题:数据可能已经在服务器更新了,但客户端还是旧的
- 同步问题:多个组件都需要同一份数据,怎么保证一致?
- 失效问题:用户在其他标签页修改了数据,当前页面怎么知道?
- 重试问题:网络请求失败了,要不要自动重试?
这些问题,useState 都解决不了。
对比表格
| 维度 | 客户端状态 | 服务端数据 |
|---|---|---|
| 真相源 | 客户端 | 服务器 |
| 生命周期 | 组件控制 | 需要同步策略 |
| 失效判断 | 不需要 | 需要(staleTime) |
| 适用工具 | useState/Zustand | React Query/SWR |
| 缓存策略 | 不需要 | 需要(cacheTime) |
这个认知的影响
现在我会这样写:
// 环境:React 18+ with @tanstack/react-query
// 场景:获取用户信息并处理缓存
import { useQuery } from '@tanstack/react-query';
function UserProfile() {
const { data: user, isLoading, error } = useQuery({
queryKey: ['user'],
queryFn: () => fetch('/api/user').then(res => res.json()),
staleTime: 5 * 60 * 1000, // 5分钟内认为数据新鲜
cacheTime: 10 * 60 * 1000, // 10分钟后清除缓存
});
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return <div>{user?.name}</div>;
}
代码变多了,但解决的问题也多了:
- ✅ 自动缓存,避免重复请求
- ✅ 失效重新获取,保证数据新鲜
- ✅ 全局共享,多个组件访问同一份数据
- ✅ 自动重试,网络异常时优雅降级
延伸思考
这让我开始思考:为什么很多教程没有强调这个区别?
可能是因为大家习惯了"状态"这个词,忽略了本质。但在实际开发中,混淆"客户端状态"和"服务端数据"会导致很多问题:
- 用户看到的数据不是最新的
- 页面切换时反复请求同一个 API
- 多个组件维护重复的数据副本
对读者的建议:
- 重新审视你项目中的
useState - 问自己:这个数据的"真相源"在哪里?
- 如果在服务器,考虑用 React Query 或 SWR
认知 2:受控组件不总是最好的
被打破的"常识"
我一直以为:
React 推荐受控组件,所以应该总是用受控组件
React 官方文档也确实强调了受控组件的好处:可以实时验证、可以控制输入等等。
在写第 8 篇时发现的问题
当我写《表单写到想摔键盘?聊聊前端常见的复杂状态场景》时,测试了一个 100 个输入框的表单:
// 环境:React 18+
// 场景:大型表单的受控组件实现
function LargeForm() {
const [values, setValues] = useState({});
// 问题:每次输入都会触发整个组件重渲染
const handleChange = (index, value) => {
setValues(prev => ({ ...prev, [index]: value }));
};
return (
<form>
{Array.from({ length: 100 }).map((_, i) => (
<input
key={i}
value={values[i] || ''}
onChange={(e) => handleChange(i, e.target.value)}
/>
))}
</form>
);
}
// 性能:用户输入时,100个输入框都重渲染
// 输入10个字符/秒 × 100个框 = 1000次渲染/秒
用 React DevTools Profiler 测试后,发现输入时明显卡顿。
React Hook Form 的选择
// 环境:React 18+ with react-hook-form
// 场景:大型表单的非受控实现
import { useForm } from 'react-hook-form';
function LargeForm() {
const { register, handleSubmit } = useForm();
const onSubmit = (data) => {
console.log(data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
{Array.from({ length: 100 }).map((_, i) => (
<input key={i} {...register(`field${i}`)} />
))}
</form>
);
}
// 性能:输入时只有当前输入框重渲染
// React Hook Form 使用非受控组件 + 按需订阅
性能提升非常明显,输入时完全流畅。
新认知
技术方案没有"应该",只有"权衡"
对比场景:
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 小表单(<10 字段) | 受控组件 | 简单直观,易于理解 |
| 大表单(>50 字段) | 非受控组件 | 性能好,避免不必要的渲染 |
| 需要实时验证 | 受控组件 | 可以在输入时立即反馈 |
| 只在提交时验证 | 非受控组件 | 性能最优 |
| 需要动态控制输入 | 受控组件 | 可以程序化控制值 |
这教会我
- 不要盲目跟随"最佳实践"
- 理解每种方案背后的权衡
- 根据具体场景选择合适的方案
对读者的建议:
- 如果你的表单很大,试试 React Hook Form
- 如果需要实时验证,受控组件仍然是好选择
- 性能问题出现时,用 Profiler 实际测试,而非凭感觉
认知 3:Context 的性能陷阱被严重低估了
我之前的误解
看到很多文章说:
Context 可以避免 Props Drilling(属性透传)
我就觉得:遇到跨层级传递数据,就用 Context。
写第 3 篇和第 8 篇时发现的问题
// 环境:React 18+
// 场景:多个状态通过 Context 共享
import { createContext, useState } from 'react';
const AppContext = createContext();
function App() {
const [user, setUser] = useState({ name: 'Alice' });
const [theme, setTheme] = useState('light');
// 问题:value 每次都是新对象
return (
<AppContext.Provider value={{ user, setUser, theme, setTheme }}>
<Header /> {/* 只需要 user */}
<Content /> {/* 只需要 theme */}
</AppContext.Provider>
);
}
// 结果:theme 变化时,Header 也会重渲染
// 即使 Header 根本不用 theme
真实的测试
我用 React DevTools Profiler 实际测试了:
- 切换主题时,整个应用都重渲染
- 即使某些组件只用了
user,跟theme完全无关
为什么?
因为 Context 的机制:只要 value 对象变了,所有消费者都重渲染。
// 每次渲染都创建新对象
const value = { user, setUser, theme, setTheme };
// 即使内容相同,引用不同,React 认为变了
解决方案对比
方案 1:拆分 Context(推荐)
// 环境:React 18+
// 场景:拆分成多个独立的 Context
const UserContext = createContext();
const ThemeContext = createContext();
function App() {
const [user, setUser] = useState({ name: 'Alice' });
const [theme, setTheme] = useState('light');
const userValue = useMemo(() => ({ user, setUser }), [user]);
const themeValue = useMemo(() => ({ theme, setTheme }), [theme]);
return (
<UserContext.Provider value={userValue}>
<ThemeContext.Provider value={themeValue}>
<Header />
<Content />
</ThemeContext.Provider>
</UserContext.Provider>
);
}
方案 2:用 Zustand 替代(推荐)
// 环境:React 18+ with zustand
// 场景:用状态管理库代替 Context
import { create } from 'zustand';
const useStore = create((set) => ({
user: { name: 'Alice' },
theme: 'light',
setUser: (user) => set({ user }),
setTheme: (theme) => set({ theme }),
}));
// Header 只订阅 user
function Header() {
const user = useStore(state => state.user);
// theme 变化时,Header 不会重渲染
return <div>{user.name}</div>;
}
// Content 只订阅 theme
function Content() {
const theme = useStore(state => state.theme);
// user 变化时,Content 不会重渲染
return <div className={theme}>Content</div>;
}
Zustand 的性能明显优于 Context,因为它支持细粒度订阅。
我的经验教训
Context 适合:
- 主题、语言设置(低频变化)
- 用户信息(登录后基本不变)
- 全局配置(几乎不变)
Context 不适合:
- 表单输入状态(高频变化)
- 动画状态(每帧都变)
- 购物车数量(频繁更新)
给读者的建议:
- 用 Context 前先问:这个数据变化频繁吗?
- 如果 >1 次/秒,考虑 Zustand 或其他方案
- 如果必须用 Context,记得用
useMemo优化 value
认知 4:AI 擅长生成代码,但不擅长架构决策
写这个系列时的 AI 使用体验
AI 帮了什么:
- ✅ 快速生成代码示例
- ✅ 解释 API 用法
- ✅ 提供多种实现方案
- ✅ 帮我整理思路和大纲
AI 没帮上的:
- ❌ 告诉我"该用哪个方案"
- ❌ 解释"为什么这样设计"
- ❌ 指出"这样写的隐患"
- ❌ 提供适合具体场景的决策
具体案例
案例 1:表单库选择
我:帮我创建一个注册表单
AI:[生成了 Formik 的代码]
我:为什么用 Formik 而不是 React Hook Form?
AI:[罗列优缺点,但没有明确建议]
最后:我自己研究后选择了 React Hook Form
案例 2:状态管理选择
我:帮我管理购物车状态
AI:[有时生成 Redux,有时生成 Context,有时生成 Zustand]
我发现:AI 的选择是随机的,没有考虑项目规模
AI 代码的常见问题
问题 1:性能优化缺失
// AI 生成的 Context 代码
const value = { user, setUser, theme, setTheme };
// 没有 useMemo,每次渲染都创建新对象 ❌
// 需要手动优化
const value = useMemo(() => ({ user, setUser }), [user]);
问题 2:边界情况未处理
// AI 生成的异步验证
validate: async (value) => {
const exists = await checkUsername(value);
return exists ? 'Username already exists' : true;
}
// 没有防抖,每次输入都请求 ❌
// 需要手动添加防抖
import { debounce } from 'lodash';
const debouncedCheck = debounce(async (value) => {
const exists = await checkUsername(value);
return exists ? 'Username already exists' : true;
}, 500);
问题 3:错误处理简陋
// AI 生成的表单提交
const onSubmit = async (data) => {
await submitForm(data);
};
// 没有 try-catch,没有 loading 状态 ❌
// 需要手动完善
const onSubmit = async (data) => {
setLoading(true);
try {
await submitForm(data);
toast.success('Submitted successfully');
} catch (error) {
toast.error(error.message);
} finally {
setLoading(false);
}
};
我总结的"AI 协作最佳实践"
明确告诉 AI 项目背景:
❌ 不好:帮我创建状态管理
✅ 好:中型电商项目(20个组件),需要管理购物车和用户状态,用 Zustand 帮我写
分步骤验证:
- 让 AI 生成基础代码
- 自己审查性能、错误处理
- 让 AI 优化特定部分
- 再次审查和测试
建立检查清单:
- 是否有性能问题?
- 是否处理了错误?
- 是否有内存泄漏?
- 类型是否安全?
- 边界情况是否考虑?
AI 的价值在哪里?
我的结论:
AI 是"代码生成器",不是"架构师"
AI 能做的:
- 快速生成样板代码
- 提供多种实现方式
- 解释技术概念
- 帮助学习新技术
人类需要做的:
- 选择合适的方案
- 优化性能和边界情况
- 建立架构规范
- 做出权衡决策
对读者的建议:
- 不要盲目信任 AI 生成的代码
- 把 AI 当作"初稿生成器"
- 最终的质量由你把控
- 保持批判性思维
认知 5:选择工具不是技术问题,是场景问题
系列开始前的想法
我以为:
学会 Redux 就能解决所有状态管理问题
写完这个系列,我意识到:
没有"最好的工具",只有"最合适的场景"
决策框架的建立
以前的思维:
遇到状态管理问题 → 用 Redux
现在的思维:
遇到问题 → 分析场景 → 选择方案
问自己:
- 数据的"真相源"在哪里?(客户端 or 服务端)
- 数据变化频率?(高频 or 低频)
- 使用范围?(单组件 or 全局)
- 项目规模?(小 or 大)
- 团队熟悉度?
我的场景 → 方案映射表
| 场景 | 我的选择 | 理由 |
|---|---|---|
| 服务端数据 | React Query | 专门处理缓存、同步、失效 |
| 全局 UI 状态 | Zustand | 简单、性能好、支持细粒度订阅 |
| 主题、语言 | Context | 低频变化,官方 API |
| 表单 | React Hook Form | 性能优先,大表单友好 |
| 通知、埋点 | EventBus | 完全解耦,跨组件通信 |
| 复杂业务逻辑 | Redux Toolkit | 标准化流程,适合大团队 |
这个认知的价值
对我写代码的影响:
以前:
// 所有状态都用 Redux
const cartItems = useSelector(state => state.cart.items);
const user = useSelector(state => state.user);
const theme = useSelector(state => state.ui.theme);
现在:
// 根据场景选择工具
const { data: user } = useQuery(['user'], fetchUser); // 服务端数据
const theme = useThemeStore(state => state.theme); // 客户端 UI
const { data: cart } = useQuery(['cart'], fetchCart); // 服务端数据
好处:
- 代码更清晰(一眼看出数据来源)
- 性能更好(各司其职)
- 维护更容易(职责分离)
给读者的建议
建立自己的决策框架:
- 列出你常遇到的场景
- 记录你的选择和理由
- 定期回顾和优化
不要追求"大一统":
- 一个项目用多种工具是正常的
- 每种工具解决特定问题
- 关键是职责分明
实用 > 完美:
- 别陷入选择困难症
- 先选一个,实践中调整
- 技术债可以还,先把功能做出来
我还困惑的问题
写完这个系列,我仍然有很多困惑:
困惑 1:服务端组件(RSC)会如何改变状态管理?
- Next.js 14 的 Server Components
- 数据直接在服务端获取
- 客户端状态会更少吗?
- React Query 还需要吗?
困惑 2:Signals 是未来吗?
- SolidJS、Preact Signals
- 比 useState 更细粒度的更新
- 会成为 React 的一部分吗?
- 现在值得学习吗?
困惑 3:AI 会如何改变前端开发?
- AI 生成代码越来越好
- 我们还需要深入学习这些吗?
- 还是应该专注于架构和业务?
困惑 4:Web3 应用的状态管理
- 区块链数据的特殊性
- 钱包连接状态
- 与传统 Web 有何不同?
这些困惑很正常:
- 技术在快速演进
- 没有人能预知未来
- 保持好奇心,持续学习
如果你对这些问题有想法,欢迎在评论区讨论。
系列回顾:这 9 篇文章的核心价值
这个系列不是教程,是思考记录。
核心价值不是"学会了什么工具",而是:
1. 建立思维框架
- 数据的生命周期
- 客户端状态 vs 服务端状态
- 受控 vs 非受控的权衡
- 场景驱动的技术选择
2. 理解设计权衡
- 没有完美方案
- 每个选择都有代价
- 关键是匹配场景
3. 培养学习方法
- 从问题出发,而非工具
- 理解"为什么",而非"怎么做"
- 实践 + 反思 + 输出
如果只记住 3 件事:
- 服务端数据 ≠ 客户端状态
- 技术选择 = 场景分析 + 权衡
- AI 是工具,架构靠思考
结语
写这个系列的初衷:
- 整理自己不熟悉的知识点
- 建立系统的认知框架
- 分享学习过程和思考
意外的收获:
- 不只是学会了工具
- 更重要的是学会了"如何思考"
- AI 的加入让学习效率提升,但也让我更清楚"人的价值"
对读者说:
如果这个系列对你有帮助,我很开心。
如果你有不同的看法,欢迎讨论。我的理解不一定对,保持开放心态。
状态管理是个大话题,我的探索才刚开始。
继续学习,持续思考。
推荐资源
官方文档(最权威)
- React 官方文档 - React 18+ 最新特性
- TanStack Query 文档 - React Query 最佳实践
- Zustand GitHub - 简洁的状态管理
- React Hook Form - 高性能表单方案
优质博客
- Kent C. Dodds - React 深度文章
- Tanner Linsley - React Query 作者
- Dan Abramov - React 核心团队
值得阅读的文章
- "You Might Not Need Redux" - Dan Abramov
- "Application State Management with React" - Kent C. Dodds
- "Practical React Query" - TkDodo
实践项目推荐
- 用 React Query 重构数据获取
- 用 Zustand 做个 Todo App
- 用 React Hook Form 做个复杂表单
这是状态管理系列的最后一篇。感谢你的阅读。