精确订阅能力对比
| 方案 | 精确订阅 | 实现方式 |
|---|---|---|
| Zustand | ✅ 支持 | 需要手动写 selector |
| Redux | ✅ 支持 | 需要手动写 selector |
| Recoil | ✅ 自动 | 天生支持,无需手动优化 |
| MobX | ✅ 自动 | 天生支持,响应式追踪 |
| Context | ❌ 不支持 | 整个 Context 变化就重渲染 |
详细对比
1. Context(React 内置)
// 定义
const AppContext = createContext({
user: null,
theme: 'light',
count: 0,
})
// 使用
function MyComponent() {
const { count } = useContext(AppContext)
// ❌ 问题:user 或 theme 变化也会导致重渲染
return
}
优点:
- ✅ React 内置,无需额外依赖
- ✅ 简单场景够用
- ✅ 适合传递静态配置
缺点:
- ❌ 无法精确订阅(Context 任何变化都触发所有消费者重渲染)
- ❌ 性能差,不适合频繁变化的状态
- ❌ 需要多个 Provider 嵌套(Context Hell)
- ❌ 没有 DevTools
适用场景:
- 主题、语言等很少变化的全局配置
- 依赖注入
2. Redux
// 定义
const initialState = {
user: null,
theme: 'light',
count: 0,
}
const rootReducer = (state = initialState, action) => {
switch (action.type) {
case 'INCREMENT':
return { ...state, count: state.count + 1 }
default:
return state
}
}
// 使用(需要手动 selector)
function MyComponent() {
// ✅ 只订阅 count,user/theme 变化不影响
const count = useSelector(state => state.count)
const dispatch = useDispatch()
return (
<div onClick={() => dispatch({ type: 'INCREMENT' })}>
{count}
)
}
// ❌ 不优化的写法
function BadComponent() {
const state = useSelector(state => state) // 整个 state 变化都重渲染
return
}
优点:
- ✅ 生态成熟,工具链完善
- ✅ Redux DevTools 强大(时间旅行)
- ✅ 支持精确订阅(用 selector)
- ✅ 可预测性强(单向数据流)
- ✅ 中间件系统强大(redux-saga、redux-thunk)
缺点:
- ❌ 样板代码多(actions、reducers、types)
- ❌ 学习曲线陡峭
- ❌ 需要手动写 selector 优化性能
- ❌ 异步处理需要额外中间件
- ❌ 代码分散(定义、action、reducer 分开)
Redux Toolkit(现代 Redux):
// 大幅简化代码
const counterSlice = createSlice({
name: 'counter',
initialState: { count: 0 },
reducers: {
increment: (state) => {
state.count += 1 // 可以直接修改(内部用 Immer)
},
},
})
const store = configureStore({
reducer: counterSlice.reducer,
})
适用场景:
- 大型复杂应用
- 需要强大的调试工具
- 需要严格的状态管理规范
- 团队协作项目
3. Zustand
// 定义
const useStore = create((set) => ({
user: null,
theme: 'light',
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}))
// 使用(需要手动 selector)
function MyComponent() {
// ✅ 只订阅 count
const count = useStore(state => state.count)
const increment = useStore(state => state.increment)
return <div onClick={increment}>{count}
}
// ❌ 不优化的写法
function BadComponent() {
const { count } = useStore() // 整个 store 变化都重渲染
return
}
优点:
- ✅ 极简,学习成本最低
- ✅ 体积极小(~1KB)
- ✅ 不需要 Provider
- ✅ 支持精确订阅(手动 selector)
- ✅ 可以在 React 外使用
- ✅ TypeScript 支持好
- ✅ 中间件支持(persist、immer、devtools)
缺点:
- ❌ 需要手动写 selector(容易忘记)
- ❌ 没有官方 DevTools(需要中间件)
- ❌ 缺少内置的异步处理方案
- ❌ 派生状态需要手动处理
适用场景:
- 中小型项目
- 快速开发
- 对包体积敏感
- 简单的全局状态
4. Recoil
// 定义
const userState = atom({
key: 'user',
default: null,
})
const themeState = atom({
key: 'theme',
default: 'light',
})
const countState = atom({
key: 'count',
default: 0,
})
// 派生状态
const doubleCountState = selector({
key: 'doubleCount',
get: ({ get }) => get(countState) * 2,
})
// 使用(天生精确订阅)
function MyComponent() {
// ✅ 自动精确订阅,user/theme 变化不影响
const count = useRecoilValue(countState)
const setCount = useSetRecoilState(countState)
return (
<div onClick={() => setCount(c => c + 1)}>
{count}
)
}
优点:
- ✅ 天生精确订阅(最大优势)
- ✅ 派生状态强大(selector)
- ✅ 原生异步支持(配合 Suspense)
- ✅ 官方 DevTools
- ✅ 原子化设计,状态隔离好
- ✅ React 并发模式友好
缺点:
- ❌ 需要 RecoilRoot Provider
- ❌ 体积较大(~14KB)
- ❌ 还在实验阶段(可能有破坏性变更)
- ❌ 学习曲线中等(atom、selector、atomFamily 等概念)
- ❌ 每个状态都需要唯一 key(容易重复)
适用场景:
- 复杂的状态依赖关系
- 需要大量派生状态
- 需要异步数据流
- Meta(Facebook)技术栈项目
5. MobX
// 定义(使用 class 或 observable)
class AppStore {
user = null
theme = 'light'
count = 0
constructor() {
makeAutoObservable(this)
}
increment() {
this.count += 1
}
get doubleCount() {
return this.count * 2
}
}
const appStore = new AppStore()
// 使用(天生精确订阅)
const MyComponent = observer(() => {
// ✅ 自动追踪依赖,只有 count 变化才重渲染
return (
<div onClick={() => appStore.increment()}>
{appStore.count}
)
})
优点:
- ✅ 自动精确订阅(响应式追踪,最智能)
- ✅ 代码最简洁(类似 Vue)
- ✅ 学习曲线平缓(OOP 思维)
- ✅ 性能优秀(自动优化)
- ✅ 支持 computed(派生值)
- ✅ 可以直接修改状态(不需要不可变更新)
- ✅ DevTools 支持
缺点:
- ❌ 魔法太多,调试困难
- ❌ TypeScript 支持一般(装饰器问题)
- ❌ 不符合 React 不可变数据理念
- ❌ 体积较大(~16KB)
- ❌ 与 React Strict Mode 有兼容问题
适用场景:
- 从 Angular/Vue 迁移的团队
- 喜欢 OOP 风格
- 复杂的领域模型
- 需要大量派生状态
实际代码对比
同一个功能:计数器 + 派生状态
好,我看到您的项目已经在用 Recoil。让我用实际代码展示各方案的区别:
实战代码对比:计数器 + 用户信息
Context(❌ 无精确订阅)
// 定义
const AppContext = createContext({
user: { name: 'John' },
count: 0,
setCount: () => {},
})
function App() {
const [count, setCount] = useState(0)
const [user, setUser] = useState({ name: 'John' })
return (
<AppContext.Provider value={{ user, count, setCount }}>
<Counter />
<UserProfile />
</AppContext.Provider>
)
}
// 使用
function Counter() {
const { count, setCount } = useContext(AppContext)
// ❌ 问题:user 变化也会导致 Counter 重渲染
console.log('Counter render')
return <button onClick={() => setCount(count + 1)}>{count}
}
function UserProfile() {
const { user } = useContext(AppContext)
// ❌ 问题:count 变化也会导致 UserProfile 重渲染
console.log('UserProfile render')
return
}
Redux(✅ 手动 selector 精确订阅)
// 定义
const slice = createSlice({
name: 'app',
initialState: {
user: { name: 'John' },
count: 0,
},
reducers: {
increment: (state) => {
state.count += 1
},
setUser: (state, action) => {
state.user = action.payload
},
},
})
// 使用
function Counter() {
// ✅ 只订阅 count,user 变化不影响
const count = useSelector(state => state.count)
const dispatch = useDispatch()
console.log('Counter render')
return <button onClick={() => dispatch(slice.actions.increment())}>{count}
}
function UserProfile() {
// ✅ 只订阅 user,count 变化不影响
const user = useSelector(state => state.user)
console.log('UserProfile render')
return
}
Zustand(✅ 手动 selector 精确订阅)
// 定义
const useStore = create((set) => ({
user: { name: 'John' },
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
setUser: (user) => set({ user }),
}))
// 使用
function Counter() {
// ✅ 只订阅 count,user 变化不影响
const count = useStore(state => state.count)
const increment = useStore(state => state.increment)
console.log('Counter render')
return <button onClick={increment}>{count}
}
function UserProfile() {
// ✅ 只订阅 user,count 变化不影响
const user = useStore(state => state.user)
console.log('UserProfile render')
return
}
// ❌ 常见错误
function BadCounter() {
const { count, increment } = useStore() // 解构整个 store
// ❌ user 变化也会重渲染
return <button onClick={increment}>{count}
}
Recoil(✅ 自动精确订阅)
// 定义(和您项目中的 userinfo.ts 类似)
const userState = atom({
key: 'user',
default: { name: 'John' },
})
const countState = atom({
key: 'count',
default: 0,
})
// 派生状态(自动缓存)
const doubleCountState = selector({
key: 'doubleCount',
get: ({ get }) => get(countState) * 2,
})
// 使用
function Counter() {
// ✅ 自动精确订阅,user 变化不影响
const count = useRecoilValue(countState)
const setCount = useSetRecoilState(countState)
console.log('Counter render')
return <button onClick={() => setCount(c => c + 1)}>{count}
}
function UserProfile() {
// ✅ 自动精确订阅,count 变化不影响
const user = useRecoilValue(userState)
console.log('UserProfile render')
return
}
function DoubleCounter() {
// ✅ 只有 count 变化时才重新计算
const double = useRecoilValue(doubleCountState)
return
}
MobX(✅ 自动精确订阅 + 响应式)
// 定义
class AppStore {
user = { name: 'John' }
count = 0
constructor() {
makeAutoObservable(this)
}
increment() {
this.count += 1
}
setUser(user) {
this.user = user
}
// 计算属性(自动缓存)
get doubleCount() {
return this.count * 2
}
}
const appStore = new AppStore()
// 使用
const Counter = observer(() => {
// ✅ 自动追踪依赖,只有 count 变化才重渲染
console.log('Counter render')
return (
<button onClick={() => appStore.increment()}>
{appStore.count}
)
})
const UserProfile = observer(() => {
// ✅ 自动追踪依赖,只有 user 变化才重渲染
console.log('UserProfile render')
return
})
const DoubleCounter = observer(() => {
// ✅ 自动追踪 count,自动缓存计算结果
return
})
复杂场景:派生状态对比
场景:学生列表过滤 + 排序
Recoil(最优雅):
const studentsState = atom({
key: 'students',
default: [],
})
const filterState = atom({
key: 'filter',
default: '',
})
const sortState = atom({
key: 'sort',
default: 'name',
})
// 自动缓存,依赖变化才重新计算
const filteredStudentsState = selector({
key: 'filteredStudents',
get: ({ get }) => {
const students = get(studentsState)
const filter = get(filterState)
const sort = get(sortState)
return students
.filter(s => s.name.includes(filter))
.sort((a, b) => a[sort] > b[sort] ? 1 : -1)
},
})
// 使用超级简单
function StudentList() {
const students = useRecoilValue(filteredStudentsState)
return
}
MobX(同样优雅):
// 定义
const AppContext = createContext({
user: { name: 'John' },
count: 0,
setCount: () => {},
})
function App() {
const [count, setCount] = useState(0)
const [user, setUser] = useState({ name: 'John' })
return (
<AppContext.Provider value={{ user, count, setCount }}>
<Counter />
<UserProfile />
</AppContext.Provider>
)
}
// 使用
function Counter() {
const { count, setCount } = useContext(AppContext)
// ❌ 问题:user 变化也会导致 Counter 重渲染
console.log('Counter render')
return <button onClick={() => setCount(count + 1)}>{count}
}
function UserProfile() {
const { user } = useContext(AppContext)
// ❌ 问题:count 变化也会导致 UserProfile 重渲染
console.log('UserProfile render')
return
}
Zustand(需要手动优化):
// 定义
const slice = createSlice({
name: 'app',
initialState: {
user: { name: 'John' },
count: 0,
},
reducers: {
increment: (state) => {
state.count += 1
},
setUser: (state, action) => {
state.user = action.payload
},
},
})
// 使用
function Counter() {
// ✅ 只订阅 count,user 变化不影响
const count = useSelector(state => state.count)
const dispatch = useDispatch()
console.log('Counter render')
return <button onClick={() => dispatch(slice.actions.increment())}>{count}
}
function UserProfile() {
// ✅ 只订阅 user,count 变化不影响
const user = useSelector(state => state.user)
console.log('UserProfile render')
return
}
总结表格
| 特性 | Context | Redux | Zustand | Recoil | MobX |
|---|---|---|---|---|---|
| 精确订阅 | ❌ | ✅ 手动 | ✅ 手动 | ✅ 自动 | ✅ 自动 |
| 体积 | 0 | ~9KB | ~1KB | ~14KB | ~16KB |
| 学习曲线 | 低 | 高 | 低 | 中 | 中 |
| 样板代码 | 少 | 多 | 少 | 中 | 少 |
| DevTools | ❌ | ✅ 强大 | ⚠️ 需中间件 | ✅ 官方 | ✅ 官方 |
| 异步支持 | ❌ | ✅ 中间件 | ⚠️ 手动 | ✅ 原生 | ✅ 原生 |
| 派生状态 | ❌ | ⚠️ reselect | ⚠️ useMemo | ✅ selector | ✅ computed |
| TypeScript | ✅ | ✅ | ✅ | ✅ | ⚠️ |
| Provider | ✅ 需要 | ✅ 需要 | ❌ | ✅ 需要 | ⚠️ 可选 |
| 成熟度 | ✅ 稳定 | ✅ 成熟 | ✅ 稳定 | ⚠️ 实验 | ✅ 成熟 |
推荐方案
根据项目规模
| 项目规模 | 推荐方案 | 原因 |
|---|---|---|
| 小型(< 10 个状态) | Zustand 或 Context | 简单够用 |
| 中型(10-50 个状态) | Zustand 或 Recoil | 平衡性能和开发效率 |
| 大型(> 50 个状态) | Redux Toolkit 或 Recoil | 需要强大工具链 |
根据团队背景
| 团队背景 | 推荐方案 |
|---|---|
| React 新手 | Zustand |
| 从 Vue 迁移 | MobX |
| 从 Angular 迁移 | MobX |
| 有 Redux 经验 | Redux Toolkit |
| Meta 技术栈 | Recoil |
根据具体需求
| 需求 | 推荐方案 |
|---|---|
| 包体积最小 | Zustand (1KB) |
| 开发速度最快 | Zustand 或 MobX |
| 调试工具最强 | Redux |
| 派生状态复杂 | Recoil 或 MobX |
| 异步逻辑复杂 | Recoil 或 Redux |
| 不想写 selector | Recoil 或 MobX |