React架构状态管理
RSC 时代的状态管理
·4 分钟阅读
格局已经变了
React Server Components 从根本上改变了状态的归属。「一切都是客户端状态」的旧心智模型不再适用。
核心洞察:大部分状态已经不属于客户端了。
决策框架
在引入任何库之前,先给状态分类:
是服务端数据? → RSC + fetch(零打包体积)
在 URL 里? → useSearchParams(可分享)
是表单? → useFormState(渐进式)
是局部 UI? → useState(够简单)
跨 3+ 组件共享? → Zustand(够轻量)
复杂且全局? → 考虑 Zustand slices
Server Components:默认选择
在 Next.js App Router 里,组件默认就是服务端的:
// 这个组件不会给客户端发送任何 JavaScript
async function UserProfile({ id }: { id: string }) {
const user = await db.user.findUnique({ where: { id } });
return (
<div>
<h1>{user.name}</h1>
<p>{user.bio}</p>
</div>
);
}没有 useEffect,没有加载状态,没有过期缓存。数据永远是新鲜的,因为它在请求时在服务端运行。
URL 状态:被低估的模式
'use client';
import { useSearchParams, useRouter } from 'next/navigation';
function Filters() {
const searchParams = useSearchParams();
const router = useRouter();
const setFilter = (key: string, value: string) => {
const params = new URLSearchParams(searchParams.toString());
params.set(key, value);
router.push(`?${params.toString()}`);
};
return (
<select
value={searchParams.get('sort') || 'newest'}
onChange={(e) => setFilter('sort', e.target.value)}
>
<option value="newest">最新</option>
<option value="popular">热门</option>
</select>
);
}好处:URL 可分享、浏览器前进后退可用、兼容 SSR、刷新后状态仍在。
什么时候不需要状态库
大部分应用根本不需要 Redux、Jotai 或 Recoil。先问自己三个问题:
- 这个状态需要在 3 个以上互相隔得很远的组件之间共享吗?
- 它需要在导航之后继续存在吗?
- 它需要中间件(日志、持久化、devtools)吗?
三个都是「不」→ 答案就是 useState。真的。
Zustand:刚好够用的那个库
当你确实需要共享的客户端状态时:
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
interface AppStore {
theme: 'light' | 'dark';
sidebarOpen: boolean;
toggleTheme: () => void;
toggleSidebar: () => void;
}
export const useAppStore = create<AppStore>()(
persist(
(set) => ({
theme: 'dark',
sidebarOpen: true,
toggleTheme: () => set((s) => ({
theme: s.theme === 'dark' ? 'light' : 'dark'
})),
toggleSidebar: () => set((s) => ({
sidebarOpen: !s.sidebarOpen
})),
}),
{ name: 'app-store' }
)
);Zustand 胜出的理由:
- 不需要 Provider 包裹
- 没有 context 重渲染问题
- TypeScript 优先
- 体积极小(1KB)
- 内置持久化、devtools、immer 中间件
反模式:过度工程
// ❌ 一个开关不要这么写
const ThemeContext = createContext();
const ThemeProvider = ({ children }) => { ... };
const useTheme = () => useContext(ThemeContext);
// 多出 3 个文件、1 个 provider,还会重渲染整棵树
// ✅ 改成这样
const useTheme = create((set) => ({
theme: 'dark',
toggle: () => set(s => ({ theme: s.theme === 'dark' ? 'light' : 'dark' }))
}));
// 1 个文件,无 provider,精准重渲染几点心得
- Server Components 消灭了大部分客户端状态 —— 让平台去干活。
- URL 状态是免费的 —— 过滤器、分页、弹窗都可以放进去。
- useState 被严重低估 —— 大部分 UI 状态本来就是局部的。
- 剩下的交给 Zustand —— API 极简,手感最好。
- 最好的状态管理是没有状态管理 —— 能推导就不要存储。