Context, Redux, Zustand, Recoil, MobX 状态管理对比

24 阅读7分钟

精确订阅能力对比

方案精确订阅实现方式
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 

{count}

}

优点:

  • ✅ 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 

{state.count}

}

优点:

  • ✅ 生态成熟,工具链完善
  • ✅ 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 

{count}

}

优点:

  • ✅ 极简,学习成本最低
  • ✅ 体积极小(~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 

{user.name}

}


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 

{user.name}

}


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 

{user.name}

}

// ❌ 常见错误

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 

{user.name}

}

function DoubleCounter() {

  // ✅ 只有 count 变化时才重新计算

  const double = useRecoilValue(doubleCountState)

  return 

{double}

}


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 

{appStore.user.name}

})

const DoubleCounter = observer(() => {

  // ✅ 自动追踪 count,自动缓存计算结果

  return 

{appStore.doubleCount}

})


复杂场景:派生状态对比

场景:学生列表过滤 + 排序

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 

{students.map(s => <div key={s.id}>{s.name}
)}

}

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 

{user.name}

}

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 

{user.name}

}


总结表格

特性ContextReduxZustandRecoilMobX
精确订阅✅ 手动✅ 手动✅ 自动✅ 自动
体积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
不想写 selectorRecoil 或 MobX