有关状态管理这个词儿已经出现好几年了。不过实际中大多数项目其实还用不上状态管理,而且即使在项目中用上,但也用得并不好,尤其是初学者,更多的是去记录各种API的用法,却不知其核心思想,很难快速实现状态管理的最佳实践。
什么是“状态管理”
之前在 Vue组件通信事件总结 这篇文章里研究过Vue里面各个组件之间的数据通信,一般页面不是特别复杂的项目,使用这篇文章总结的通信方式足够了,但当前端页面复杂度越来越高,props、$emit、$listeners、ref / refs 、eventBus这些方法也许还不够方便,这个时候可以开始尝试vuex的状态管理机制了。
用一个通俗易懂的概念来解释状态管理:全局变量。
但是在项目的开发中,我们应该都记得前辈的叮嘱:尽量不要或少用全局变量,因为那是不可控的操作,任意的操作都可能导致数据的改变,造成全局污染,无法追踪数据变化过程,所以开发中我们小菜鸟也大都避免去过多的操作全局变量。
但当项目复杂度比较高时,也许多个组件、多个页面之间需要实现一种数据或状态的共享,这时候,我们就可以将这些状态统一的进行管理,既可以实现共享状态,又能追踪到数据的变化,可能这也是状态管理诞生的一种原因吧。
当然,跟全局变量一样,各个文章里面也是叮而嘱之,如果没有哦必要,尽量不要使用状态管理。
状态管理应用包含三部分:
- state,驱动应用的数据源;
- view,以声明方式将 state 映射到视图;
- actions,响应在 view 上的用户输入导致的状态变化。
Flux 架构思想
关于如何在UI试图中合理地修改state,Facebook提出了flux思想。
Flux 是一种架构思想,专门解决软件的结构问题。后来的rudux和vux也都是由Flux的思想演化出来的。
Flux将一个应用分成四个部分: View(视图)、Action(动作)、Dispatcher(派发)、Store(数据)

整体流程:
这是一个单向流动的过程,
用户访问view -> 触发事件action -> Dispatcher知道后告知store要更新 -> store更新,触发视图View更新页面
特点:
- 单向数据流
- Store 可以有多个
- Store 不仅存放数据,还封装了处理数据的方法(即可以直接修改数据状态)
Redux
redux的核心概念:
Redux store 是单一数据源,且store是不可变的,Redux 没有 dispatcher 的概念,

特点:
- 单向数据流
- 单一数据源,只有一个store
- state只读
- 没有 Dispatcher ,而是在 Store 中集成了 dispatch 方法,
store.dispatch()是 View 发出 Action 的唯一途径
Store 中提供了几个管理 state 的 API:
store.getState():获取当前 statestore.dispatch(action):触发state改变(唯一途径)store.subscribe(listener):设置state变化的监听函数(若把视图更新函数作为 listener 传入,则可触发视图自动渲染)
整体流程:
Action Creator -> 触发action -> store.dispatch(action) -> reducer(state, action) -> 更新state
MobX
MobX 是通过透明的函数响应式编程使得状态管理变得简单和可扩展,是一个用法简单优雅、同时具有可扩展性的状态管理库。

和 Redux 对单向数据流的严格规范不同,Mobx 只专注于从 store 到 view 的过程。在 Redux 中,数据的变更需要监听,而 Mobx 的数据依赖是基于运行时的,这点和 Vuex 更为接近。
如果大家使用过 Vue 的话相信对其双向绑定 MVVM 的思想并不陌生,React + Mobx 相当于是 Vue 全局作用域下的双向绑定,而 Vue 的状态管理框架 Vuex 却是借鉴了 Flux 架构,连尤大都说,似乎有点你中有我,我中有你的关系。
特点:
- 往往是多个 Store
- 数据流流动不自然,只有用到的数据才会引发绑定,局部精确更新
- 一般适合应用于中小型项目中
Vuex
仍然放一张vuex的示意图:

vuex的运作原理跟redux稍有不同。因为Vue虽然是单向数据流,但Vue 基于 ES5 中的 getter/setter 来实现视图和数据的双向绑定,因此 Vuex 中 state 的变更可以通过 setter 通知到视图中对应的指令来实现视图更新。且state不是通过actions来修改的,而是通过mutations。
特点:
-
单向数据流, 通过
store.dispatch()调用 Action ,在 Action 执行完异步操作之后通过store.commit()调用 Mutation 更新 State -
单一数据源
-
actions 可以是 异步操作,故可在action中调用后台接口获取新的数据;
-
mutations 只能是 同步操作;
-
mutations 和 actions 都可直接更改 state,但是当 action 含有异步操作时,会使得数据变化混乱,难以跟踪,使得调试困难;基于以上原因,Vuex 规定只能是 mutations 来改变 state。
整体流程:
用户访问view -> 触发事件action ->action中触发对应的mutation -> 在mutation函数中改变state -> 通过 getter/setter 实现的双向绑定的机制,视图View会自动更新页面
使用一句话总结:commit mutation,dispatch action
几种方案的异同
从上面几种热门的状态管理实现方式来看,可以发现,这几种数据流模型几乎都是从 action 到 view 之间的一种数据流动,总的过程可以大致简化为:
action -> 更新 state(视图层)
1、 数据状态中心: state和store
可以发现,在redux和flux中,数据状态用store表示,而在vuex和mobx中,这个store被state替代。
和 Redux 中使用不可变数据来表示 state 不同,Vuex 中没有 reducer 来生成全新的 state 来替换旧的 state,Vuex 中的 state 是可以被修改的。
Vuex 中的 state 是可修改的,而修改 state 的方式不是通过 actions,而是通过 mutations。更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。
2、 行为action
我们想要更新视图内容,第一步就是要有触发的动作,action就是向store发起更新请求的最小单元。但action并非是一个动词,它是一个行为的描述,本质上是一个纯声明式的数据结构,仅提供对事件的描述,不提供事件的具体逻辑
在rudux中,生成action有两种方式:
-
声明对象:
const action = { type: 'ADD_TODO', text: '生成一个action' } dispatch(action) -
函数生成:
function addTodo(text) { return { type: ADD_TODO, text } } dispatch(addTodo())
3、dispatch
在各类应用状态管理的模型中,通常都会有一个dispatch方法,它就声明在Store上,负责调用各个Action,然后由Store上对应的分发机制进行处理。
4、Reducer / Mutation
现在到了我们的第三步,更新状态。flux和mobx对状态更新的处理是直接更改,而redux和vuex中间还会多出一个reducer和mutation来处理更新的工作。
至于reducer和mutation,分别是针对redux和vuex的不同工作机制。
先看reducer,Actions 只能描述发生了什么,并不能描述状态发生了什么变化,Reducers 指定 state tree 将要发生什么,它主要的工作内容是:接受一个action,然后通过switch匹配action的type,作出相应的处理后,返回一个新的对象。为什么是新的对象呢?因为redux最核心的一个概念,store是不可变的, Redux 是一个实践函数式编程(FP)理念的库。下面是一个最简单的reducer:
const items = (state = [], action) => {
switch (action.type) {
case "ADD_ITEM":
return [...state, { text: action.text }]
default:
return state
}
}
再来看mutation,一个 mutation 是由一个 type 和与其对应的 handler 构成的,type 是一个字符串类型用以作为 key 去识别具体的某个 mutation,handler 则是对 state 实际进行变更的函数。
// store
const store = {
books: []
}
// mutations
const mutations = {
[ADD_BOOKS](state, book) {
state.books.push(book)
}
}