技术知识文章集合TECHNICAL ARCHIVE · 457 DOCUMENTS

显示模式

登录
ARCHIVE DOCUMENTVUE

Vuex、Flux、Redux、Redux-Saga、Dva、MobX:状态管理思想与现代实践

所属馆藏
Vue
文件格式
Markdown
原始路径
Vue/37-Vuex, Flux, Redux, Redux-saga, Dva, MobX
本文目录17 个章节
  1. 一、为什么需要状态管理?
  2. 二、最简单的 Store 模式
  3. 三、Flux:单向数据流思想
  4. 四、Redux:单 Store、Action 和 Reducer
  5. 五、Redux 与 Flux 的差异
  6. 六、Redux Middleware 和异步处理
  7. 七、Redux-Saga:用 Generator 描述副作用
  8. 八、Dva:Redux + Redux-Saga 的历史封装
  9. 九、Vuex:Vue 生态中的集中式状态管理
  10. 十、React-Redux 与现代 Redux UI 绑定
  11. 十一、MobX:可观察状态与反应式派生
  12. 十二、Redux 与 MobX 的对比
  13. 十三、当前 Vue 项目的选择建议
  14. 十四、原文中容易误解的说法
  15. 十五、总结
  16. 官方参考
  17. 原文出处

Vuex、Flux、Redux、Redux-Saga、Dva、MobX:状态管理思想与现代实践

原文试图把 Store、Flux、Redux、Vuex、Redux-Saga、Dva 和 MobX 放在一条演进路径中。这个主线仍有学习价值,但原文的部分示例是抓取后的损坏代码,且 Redux、Vuex、Pinia、MobX 的推荐用法已经变化。

本文尽量保留原文的概念、比较和历史代码,同时区分:

  • 历史模型:用于理解 Flux/Redux/Vuex 早期设计;
  • 当前推荐:Redux Toolkit、Pinia、MobX 6 等新项目写法;
  • Vue 2/3 边界:Vuex 3/4 与 Pinia 2/3 不能混为一谈。

一、为什么需要状态管理?

不管使用 Vue、React 还是其他 UI 框架,应用都需要管理状态(state):

  • 一个组件需要读取另一个组件的数据;
  • 一个组件需要改变另一个组件使用的数据;
  • 多个页面共享登录用户、权限、购物车、主题或请求缓存;
  • 状态变化需要可追踪、可调试、可测试。

父子组件可以通过 props/emits 通信,兄弟组件可以提升状态到共同父组件,跨层级可以使用 provide/inject。问题变复杂后,如果每个组件都能随意改全局变量,就很难回答:

  • 这个状态什么时候改变的?
  • 是谁改变的?
  • 变化是否符合业务规则?
  • 页面为什么重新渲染?
  • 如何在测试中复现某一次状态变化?

状态管理模式的基本思路是:

把需要共享的状态抽取出来,使用明确的更新约定,让状态变化可预测,并让 UI 订阅状态变化。

这不是说所有状态都应该放进 Store。只被一个组件使用的输入框、弹窗开关和临时表单状态,通常放在组件本地更简单。

二、最简单的 Store 模式

最简单的实现是把状态放到外部变量中,例如 Vue 2 时代常见的 this.$root.$data 或模块级变量:

const state = {
  message: 'Hello!',
}

function setMessage(value) {
  state.message = value
}

function clearMessage() {
  state.message = ''
}

如果所有代码都能直接修改 state.message,状态变化就没有统一入口。于是可以约定组件不直接修改 state,而是通过 action/function 修改:

const store = {
  state: {
    message: 'Hello!',
  },
  setMessage(value) {
    this.state.message = value
  },
  clearMessage() {
    this.state.message = ''
  },
}

这样做至少有几个好处:

  • 可以在统一入口打印日志;
  • 可以增加权限、校验和持久化;
  • 可以在测试中单独调用更新函数;
  • 可以进一步演进成单向数据流。

这仍然只是约定。如果 store.state.message = 'x' 仍然公开可写,任何组件都可以绕过 action。严格限制需要库提供运行时约束或团队规范。

原文 Store 模式配图(本地化,历史资料)

三、Flux:单向数据流思想

Flux 不是一个唯一的库,而是一种架构思想。经典 Flux 通常包含:

View -> Action -> Dispatcher -> Store -> View

它可以描述为四个部分:

  • View:展示 Store 中的数据,也产生用户操作;
  • Action:描述发生了什么,例如 ADD_USER
  • Dispatcher:接收 action,并分发给注册的 Store;
  • Store:保存状态,根据 action 更新自身并通知 View。

原文 Flux 流程图(本地化,历史资料)

Flux 的核心约束是数据单向流动:

  1. View 产生 action;
  2. Dispatcher 把 action 分发给 Store;
  3. Store 根据 action 更新状态;
  4. Store 变化后通知 View;
  5. View 重新读取 Store。

原文说 Dispatcher 会把所有 Action 发给所有 Store,这符合经典 Flux Dispatcher 的注册回调模型。实际框架可以在 Store 内判断自己是否关心这个 action,也可以使用更细的事件路由;不要把“所有 Store 都必须处理所有 action”理解成硬性要求。

Flux 解决的是可追踪和单向流动问题,但多个 Store 之间的依赖、异步处理和样板代码也会增加复杂度。Redux 从 Flux 中吸收了单向数据流思想,同时简化了 Dispatcher/Store 的组织方式。

四、Redux:单 Store、Action 和 Reducer

Redux 与具体 UI 框架无关,可以和 React、Vue、Angular 或原生 JavaScript 配合。它的历史核心模型是:

View -> dispatch(action) -> reducer(state, action)
     -> new state -> subscribe/render View

原文 Redux 架构图(本地化,历史资料)

1. Store

经典 Redux Store 提供三个主要方法:

  • getState():读取当前 state;
  • dispatch(action):分发 action;
  • subscribe(listener):订阅 state 变化。

Redux 核心要求通过 dispatch 触发更新,不能直接修改 Store 内部 state。现代 Redux 项目通常使用 Redux Toolkit 的 configureStore,不建议手写底层初始化:

import { configureStore, createSlice } from '@reduxjs/toolkit'

export const counterSlice = createSlice({
  name: 'counter',
  initialState: { value: 0 },
  reducers: {
    increment(state) {
      // Redux Toolkit 使用 Immer,允许写出看似可变的更新
      state.value += 1
    },
    decrement(state) {
      state.value -= 1
    },
  },
})

export const store = configureStore({
  reducer: {
    counter: counterSlice.reducer,
  },
})

store.dispatch(counterSlice.actions.increment())
console.log(store.getState().counter.value)

Redux Toolkit 的 case reducer 可以写 state.value += 1,但底层仍会生成不可变的新 state。不要把这段代码理解为 Redux 核心允许业务代码直接修改外部 state。

2. Action

Action 通常是一个至少包含 type 的普通对象:

const action = {
  type: 'todos/todoAdded',
  payload: {
    text: 'Learn Redux',
  },
}

Action 描述“发生了什么”,不应该直接保存需要执行的 UI 操作。Redux Toolkit 的 createSlice 会自动生成 action creator 和 action type。

3. Reducer

Reducer 是接收 (previousState, action) 并返回下一个 state 的函数:

function counterReducer(state = { value: 0 }, action) {
  switch (action.type) {
    case 'counter/incremented':
      return { value: state.value + 1 }
    case 'counter/decremented':
      return { value: state.value - 1 }
    default:
      return state
  }
}

传统 Redux reducer 需要满足:

  • 对相同输入得到相同输出;
  • 不修改传入的 state;
  • 不执行网络请求、随机数、时间读取等不可控副作用;
  • 不认识的 action 返回原 state;
  • 状态没有变化时尽量返回原引用,便于订阅者判断。

Redux Toolkit 通过 Immer 降低不可变更新的书写成本,但 reducer 仍应保持纯粹。异步请求应放在 thunk、middleware、listener middleware、RTK Query 或其他明确的副作用层。

4. 一个用于学习的 createStore

下面的实现只用于理解原理,缺少 Redux 正式实现中的初始化 action、错误检查、listener 快照、嵌套 dispatch 约束等,不应直接用于生产:

function createStoreForLearning(reducer) {
  let state = reducer(undefined, { type: '@@init' })
  let listeners = []

  function getState() {
    return state
  }

  function dispatch(action) {
    state = reducer(state, action)
    listeners.slice().forEach(listener => listener())
    return action
  }

  function subscribe(listener) {
    listeners.push(listener)
    return () => {
      listeners = listeners.filter(item => item !== listener)
    }
  }

  return { getState, dispatch, subscribe }
}

5. combineReducers

大型应用通常把状态拆成多个 slice reducer,再合成 root reducer:

import { combineReducers } from 'redux'

const rootReducer = combineReducers({
  todos: todosReducer,
  visibilityFilter: visibilityFilterReducer,
})

Redux Toolkit 的 configureStore({ reducer: { todos, users } }) 会自动完成常见组合。原文中的简化 combineReducers 只适合解释“每个 reducer 维护 state 的一部分”,不应替代 Redux 正式实现的 key 校验和状态变化检查。

原文 Redux 流程图(本地化,历史资料)

五、Redux 与 Flux 的差异

原文 Flux/Redux 对比图(本地化,历史资料)

原文 Redux 子 reducer 对比图(本地化,历史资料)

可以把经典差异总结为:

项目经典 Flux经典 Redux
Store通常多个 Store通常一个 root Store,内部拆分 reducer
Dispatcher独立 Dispatcherstore.dispatch 集成分发入口
更新Store 响应 actionreducer 根据 state/action 返回新 state
不可变性依赖实现和约定传统 Redux 强调不可变更新,RTK 用 Immer 简化
副作用由实现决定middleware/thunk/saga 等放在 reducer 外
调试依赖实现Redux DevTools、action 日志、状态回放生态成熟

“Redux 只能有一个 Store”是经典架构约定,不是 JavaScript 层面无法创建多个 Store。新项目通常仍使用一个根 Store,以保持数据流和 DevTools 追踪的一致性。

六、Redux Middleware 和异步处理

Reducer 必须是纯函数,而网络请求、延时、日志、错误上报等属于副作用。Redux middleware 位于 dispatch 和 reducer 之间,典型签名是:

const logger = store => next => action => {
  console.log('dispatching', action)
  const result = next(action)
  console.log('next state', store.getState())
  return result
}

middleware 可以:

  • 在 action 到达 reducer 前读取或修改它;
  • 让 dispatch 接受函数、Promise 或其他自定义值;
  • 在请求开始/成功/失败时分发多个普通 action;
  • 统一日志、错误和取消逻辑。

Redux Toolkit 的默认方案

Redux Toolkit 的 configureStore 默认包含 redux-thunk,新项目通常使用 createAsyncThunk、RTK Query 或 listener middleware:

import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'

export const fetchUser = createAsyncThunk(
  'users/fetchUser',
  async (userId: string, thunkApi) => {
    const response = await fetch(`/api/users/${userId}`, {
      signal: thunkApi.signal,
    })

    if (!response.ok) {
      throw new Error(`HTTP ${response.status}`)
    }

    return response.json()
  },
)

createAsyncThunk 会生成 pending/fulfilled/rejected 生命周期 action,并提供 requestId、abort signal 等能力。它比手动拼接每个请求 action 更不容易漏掉 loading/error 分支。

Redux Thunk(历史与当前)

Thunk 允许 dispatch 一个函数,由 middleware 注入 dispatchgetState

const fetchData = id => async (dispatch, getState) => {
  dispatch({ type: 'data/fetchStarted', payload: id })

  try {
    const response = await fetch(`/api/data/${id}`)
    if (!response.ok) throw new Error(`HTTP ${response.status}`)
    const data = await response.json()
    dispatch({ type: 'data/fetchSucceeded', payload: data })
  } catch (error) {
    dispatch({ type: 'data/fetchFailed', error: String(error) })
  }
}

Thunk 自由度高,但请求开始、成功、失败的 action 和竞态处理需要业务自己设计。RTK 的 createAsyncThunk 是官方推荐的标准化封装。

Redux Promise(第三方历史方案)

redux-promise 等第三方 middleware 可以识别 Promise payload,并约定成功/失败 action。它不是 Redux 核心 API,也不是当前 Redux 官方默认方案。采用它时要核对项目依赖维护状态、错误 action 格式和取消请求语义,不能把“Promise 自动 dispatch”写成 Redux 原生能力。

七、Redux-Saga:用 Generator 描述副作用

Redux-Saga 是 Redux 的第三方 side-effect middleware,使用 Generator 和 effect 描述异步流程。它不是 Vue 的状态管理库,也不是 Redux Toolkit 的必需依赖。

原文用 Generator 说明“暂停和恢复”:

function* helloWorldGenerator() {
  yield 'hello'
  yield 'world'
  return 'ending'
}

const iterator = helloWorldGenerator()
iterator.next() // { value: 'hello', done: false }
iterator.next() // { value: 'world', done: false }
iterator.next() // { value: 'ending', done: true }

Saga middleware 并不是直接把 Promise 变成同步请求,而是解释 Generator yield 出来的 effect 描述对象,并在完成后把结果送回 Generator:

import {
  call,
  put,
  takeLatest,
} from 'redux-saga/effects'

function* fetchUserSaga(action) {
  try {
    yield put({ type: 'users/fetchStarted' })
    const data = yield call(fetchUser, action.payload)
    yield put({ type: 'users/fetchSucceeded', payload: data })
  } catch (error) {
    yield put({ type: 'users/fetchFailed', error: String(error) })
  }
}

function* rootSaga() {
  yield takeLatest('users/fetchRequested', fetchUserSaga)
}

原文的 put(SHOW_LOADING_ACTION, { isLoading: true }) 不是常规写法;put 通常接收一个 action 对象:

yield put({
  type: 'users/fetchStarted',
  payload: { isLoading: true },
})

常见 effect:

  • take:等待指定 action;
  • put:分发 action;
  • call:调用函数并等待结果;
  • fork:启动非阻塞任务;
  • cancel:取消任务;
  • takeEvery:每次 action 都启动任务;
  • takeLatest:启动新任务并取消上一个 saga 任务;
  • all:并行等待多个 effect;
  • race:竞争多个 effect,第一个完成后结束其他任务。

现代 Redux-Saga 并行写法使用 all

import { all, call, race, delay } from 'redux-saga/effects'

function* loadInParallel() {
  const [users, repos] = yield all([
    call(fetch, '/users'),
    call(fetch, '/repos'),
  ])

  const result = yield race({
    posts: call(fetchPosts),
    timeout: delay(3000),
  })

  return { users, repos, result }
}

旧版 Redux-Saga 允许直接 yield effect 数组;当前文档更常使用 all([...]),应以安装版本为准。

原文 thunk/saga 对比配图(本地化,历史资料)

原文异步中间件配图(本地化,历史资料)

原文 Saga 流程配图(本地化,历史资料)

takeLatest 取消的是 saga task;底层 fetch 是否真正停止,取决于请求函数是否接收并使用 AbortSignal,不能把任务取消自动等同于网络连接已取消。

Redux-Saga 的优点是:

  • 异步业务逻辑集中在 saga 文件;
  • action 仍然可以保持普通对象;
  • Generator effect 便于测试每一步的描述;
  • 可以细致控制并发、取消、重试和竞争。

代价是需要学习 Generator、effect、task 生命周期和取消语义。请求逻辑简单时,RTK createAsyncThunk 或 RTK Query 通常更直接。

八、Dva:Redux + Redux-Saga 的历史封装

Dva 是面向 React 的数据流方案,建立在 Redux、redux-saga 等技术之上,并提供 model、subscription 和路由/请求相关集成。它不是 Vue 的状态管理库。

原文对 Dva 的核心理解是正确的:把 reducer、effect、subscription 和 state 按业务 model 组织在一起,减少 action/reducer/saga 文件之间的来回切换。

原文 Dva TODO 流程配图(本地化,历史资料)

一个更接近 Dva 传统写法的示意:

app.model({
  namespace: 'products',
  state: {
    list: [],
    loading: false,
  },
  subscriptions: {
    setup({ dispatch }) {
      // subscription 在 model 外部 dispatch 时使用完整 namespace
      dispatch({ type: 'products/query' })
    },
  },
  effects: {
    *query({ payload }, { call, put }) {
      yield put({ type: 'queryStarted' })
      try {
        const list = yield call(fetchProducts, payload)
        yield put({ type: 'querySucceeded', payload: list })
      } catch (error) {
        yield put({ type: 'queryFailed', error: String(error) })
      }
    },
  },
  reducers: {
    queryStarted(state) {
      return { ...state, loading: true }
    },
    querySucceeded(state, { payload }) {
      return { ...state, loading: false, list: payload }
    },
    queryFailed(state) {
      return { ...state, loading: false }
    },
  },
})

原文中的 subscriptions 有时写成数组,但 Dva model 常见 API 是对象;effects 中的 generator 也需要正确使用 callput 和 action payload。具体以项目使用的 Dva 版本文档为准。

原文 Dva model 配图(本地化,历史资料)

Dva 适合被当作 React/Redux-Saga 生态的历史架构学习。其官方仓库和社区活跃度不能与当前主流 React/Redux Toolkit 方案等量齐观,新项目不应仅因为原文示例就默认采用 Dva;迁移或维护旧项目时应先锁定版本、检查 release 和插件兼容性。

九、Vuex:Vue 生态中的集中式状态管理

Vuex 与 Flux/Redux 都强调集中状态和可预测更新,但它使用 Vue 的响应式系统,传统 API 将同步更新放进 mutation,将异步流程放进 action。

原文 Vuex 架构配图(本地化,历史资料)

1. Vuex 3(Vue 2)历史用法

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    count: 1,
  },
  mutations: {
    increment(state) {
      state.count++
    },
  },
  actions: {
    async incrementAsync({ commit }) {
      await delay(300)
      commit('increment')
    },
  },
  getters: {
    doubleCount: state => state.count * 2,
  },
})

组件通过 this.$store.commit('increment') 提交 mutation,通过 this.$store.dispatch('incrementAsync') 分发 action。mutation 应保持同步,这样 DevTools 才能准确记录一次状态变化;网络请求、定时器等放入 action 或外部服务。

2. Vuex 4(Vue 3)用法

import { createStore } from 'vuex'

export const store = createStore({
  state: () => ({
    count: 1,
  }),
  mutations: {
    increment(state) {
      state.count++
    },
  },
  actions: {
    incrementAsync({ commit }) {
      return new Promise(resolve => {
        setTimeout(() => {
          commit('increment')
          resolve()
        }, 300)
      })
    },
  },
  getters: {
    doubleCount: state => state.count * 2,
  },
})
import { createApp } from 'vue'
import App from './App.vue'
import { store } from './store'

createApp(App).use(store).mount('#app')

Vuex 4 的 API 主要是 Vuex 3 的 Vue 3 适配,不能把 new Vue({ store }) 直接当作 Vue 3 入口。Vuex 模块仍可拆分 state、getters、mutations 和 actions。

3. Vuex 的组成

  • state:共享数据;
  • getter:从 state 派生可复用结果;
  • mutation:同步改变 state 的明确入口;
  • action:处理异步或组合多个 mutation;
  • module:拆分大型 Store。

原文“Vuex 的 state 不能直接改”是更新约定。Vuex 在开发环境可以通过 strict mode 检测非 mutation 修改,但 JavaScript 本身不会让对象彻底不可写。开启 strict mode 会增加深度 watch 成本,不建议生产环境无条件开启。

4. Vuex、Pinia 和版本状态

Vuex 官方文档目前把 Pinia 作为 Vue 官方状态管理的默认推荐,并说明 Vuex 3/4 仍会维护但不太可能增加新功能。Pinia 可以视作 Vuex 5 RFC 方向的独立实现,但“Vuex 5 一定会发布”或“Pinia 就是 Vuex 5 正式版本”都不严谨。

  • Vuex 3:主要用于 Vue 2;
  • Vuex 4:主要用于 Vue 3;
  • Pinia 2:可用于 Vue 2.7/Vue 3 的旧兼容项目;
  • Pinia 3:面向 Vue 3,当前新 Vue 3 项目优先考虑 Pinia;
  • 不要为了新项目继续照搬 Vuex mutation/action 样板,先评估 Pinia 是否更合适。

十、React-Redux 与现代 Redux UI 绑定

Redux 本身不负责 UI 绑定。React 项目过去常使用 react-reduxProviderconnectmapStateToPropsmapDispatchToProps

原文 React-Redux 配图(本地化,历史资料)

import { Provider, useDispatch, useSelector } from 'react-redux'
import { store } from './store'
import { counterSlice } from './counterSlice'

function Counter() {
  const value = useSelector(state => state.counter.value)
  const dispatch = useDispatch()

  return (
    <button onClick={() => dispatch(counterSlice.actions.increment())}>
      {value}
    </button>
  )
}

export default function App() {
  return (
    <Provider store={store}>
      <Counter />
    </Provider>
  )
}

connect 仍可用于维护旧代码,但当前 React-Redux 文档更常使用 hooks。原文把 React-Redux 说成“React 官方出的”不准确:它是 Redux 生态的官方维护绑定库,由 Redux 组织维护,不是 React 核心包。

十一、MobX:可观察状态与反应式派生

Redux/Flux 更强调显式 action、纯 reducer 和单向流;MobX 的核心思想是:

状态变化后,读取这些状态的计算和视图自动重新运行。

依赖关系可以概括为:

observable state -> computed / reaction / observer
                -> state 改变后只重新运行相关反应

原文中的 autoRun 不是 MobX 公共 API,正确名称是 autorun。MobX 6 新项目通常使用 makeAutoObservable,而不是把旧装饰器示例直接复制到 Babel/TypeScript 配置中:

import {
  autorun,
  makeAutoObservable,
} from 'mobx'

class CounterStore {
  count = 0
  user: unknown = null

  constructor() {
    makeAutoObservable(this)
  }

  get doubled() {
    return this.count * 2
  }

  increment() {
    this.count++
  }
}

const store = new CounterStore()
const dispose = autorun(() => {
  console.log(store.count, store.doubled)
})

store.increment()
dispose()

makeAutoObservable 会根据成员自动推断 observable、computed 和 action。异步操作完成后如果要在 action 外修改多个状态,可以使用 runInAction

import { runInAction } from 'mobx'

async function loadUser() {
  const user = await fetch('/api/user').then(response => response.json())

  runInAction(() => {
    store.user = user
  })
}

旧装饰器写法的版本边界

原文使用:

class Store {
  @observable a = 0

  @action
  increment() {
    this.a++
  }
}

这属于 MobX 早期/装饰器配置相关写法。MobX 6 仍可以在特定现代 decorators 或 legacy decorators 配置下使用装饰器,但必须明确配置和初始化方式。不要把上面的代码当作“无需配置即可运行”的当前示例。

如果不希望依赖装饰器编译配置,可以显式声明注解:

import {
  action,
  computed,
  makeObservable,
  observable,
} from 'mobx'

class Todo {
  finished = false
  title = ''

  constructor() {
    makeObservable(this, {
      finished: observable,
      label: computed,
      toggle: action,
    })
  }

  get label() {
    return `${this.title}: ${this.finished ? 'done' : 'open'}`
  }

  toggle() {
    this.finished = !this.finished
  }
}

如果确实使用 MobX 6 的现代 Stage-3 decorators,必须开启对应的 TypeScript/Babel 配置,并使用 accessor

import { action, computed, observable } from 'mobx'

class ModernTodo {
  @observable accessor finished = false
  title = ''

  @computed get label() {
    return `${this.title}: ${this.finished ? 'done' : 'open'}`
  }

  @action toggle() {
    this.finished = !this.finished
  }
}

MobX 与 React 常使用 mobx-react-liteobserver 包装函数组件;Vue 项目不应因为“MobX 可以跨框架”就自动引入它,Vue 3 通常优先使用 Pinia 或组合式状态。

原文 MobX 响应式配图(本地化,历史资料)

十二、Redux 与 MobX 的对比

原文计数器示例截图(本地化,历史资料)

对比项Redux/Redux ToolkitMobX
核心思路action 描述变化,reducer 产生新 stateobservable 状态驱动 computed/reaction
更新风格传统 Redux 偏不可变;RTK 使用 Immer 简化通常在 action 中直接改变 observable
数据流显式、可序列化、易回放隐式依赖追踪,视图更新粒度细
副作用thunk、listener、saga、RTK Query 等action、flow、reaction 或业务服务
样板量RTK 已显著减少传统 Redux 样板通常较少,但需要理解响应式依赖
调试重点action 日志、时间旅行、状态快照observable、reaction、action 边界

不能简单得出“小项目用 MobX,大项目用 Redux”这样的固定结论。选择应该考虑团队经验、状态是否需要可序列化、调试和回放需求、异步数据缓存、SSR、生态和维护成本。

十三、当前 Vue 项目的选择建议

当前 Vue 3.5 项目可以按状态范围选择:

  1. 组件本地状态refreactivecomputed
  2. 父子/跨层级依赖:props/emits、provide/inject;
  3. 多个页面共享的客户端状态:Pinia;
  4. 服务端数据缓存:结合请求库或专门的 query/cache 方案,不要把所有请求结果无期限塞进全局 Store;
  5. 维护 Vue 2 项目:Vuex 3 或 Pinia 2,按项目现状和迁移计划决定;
  6. React 旧项目:Redux/React-Redux、redux-thunk、redux-saga、Dva 等需按锁定版本维护;
  7. React 新项目:优先 Redux Toolkit + React-Redux,是否使用 RTK Query、listener middleware 或 Saga 取决于异步复杂度。

状态管理库不是越多越好。不要在一个 Vue 3 项目中同时引入 Vuex、Pinia、MobX、mitt,只为了“共享数据更灵活”;重复的数据源会带来同步、SSR 隔离和调试困难。

十四、原文中容易误解的说法

  • “Redux 必须每次手写完整不可变更新”是传统 API 的体验;当前官方推荐 Redux Toolkit,Immer 会简化 case reducer。
  • “Redux 的 reducer 可以处理异步”不准确;异步应在 middleware、thunk、listener、saga 或请求缓存层。
  • “redux-saga 的 takeLatest 一定取消 HTTP 请求”不准确;它主要取消 saga task,网络请求需要配合 AbortSignal。
  • “Dva 是 Vue 的状态管理方案”错误;Dva 面向 React/Redux-Saga 生态。
  • “MobX 的 autoRun API”错误,应为 autorun
  • “MobX 装饰器代码开箱即用”过时;MobX 6 需要按 decorators 配置,推荐先考虑 makeAutoObservable
  • “Vuex 5 会按原计划发布”不应作为事实;Pinia 是官方当前新 Vue 项目推荐方案。
  • “所有状态都必须放 Store”会造成过度集中;组件本地状态应留在组件。
  • “middleware 就是简单包裹 dispatch”只是直观模型;正式 middleware 还要遵守 store => next => action 链式传递和返回值语义。

十五、总结

原文最值得保留的主线是:

共享状态
  -> 统一入口
  -> 可预测更新
  -> UI 订阅变化

Flux 解释单向数据流,Redux 把 action/reducer/store 约束做得更明确,middleware 负责把副作用放到 reducer 外;Vuex 将类似思想与 Vue 响应式系统结合;Redux-Saga 使用 Generator 描述复杂副作用;Dva 把 React/Redux-Saga 的多个文件按 model 聚合;MobX 则通过可观察状态和依赖追踪减少显式样板。

对当前 Vue 3.5 项目而言,优先选择最小且清晰的方案:组件状态用 Vue API,跨页面共享状态用 Pinia,服务端数据用合适的缓存层。学习 Flux、Redux、Vuex、Saga、Dva 和 MobX 的价值在于理解状态流动和副作用边界,而不是把所有库同时引入项目。

官方参考

Vue 与 Pinia/Vuex

Redux 与 Redux Toolkit

Redux-Saga 与 Dva

MobX

原文出处

原文标题:Vuex, Flux, Redux, Redux-saga, Dva, MobX

原文来源:知乎专栏配图及文章整理。本文保留原文的历史概念和示例方向,并对抓取损坏、版本过时和错误 API 进行了修订。

457 DOCUMENTS · 10 COLLECTIONS
ARCHIVE SEARCH457 篇文章

SEARCH GUIDE

输入关键词开始搜索

支持搜索文章标题、所属分类和原始文档路径。

按分类浏览

10 COLLECTIONS