flux&redux&mobx&vuex

372 阅读8分钟

为什么使用状态管理库

如今的现状是要用组件(components)来构建一个前端应用。组件有自己的内部状态。举个栗子,在 React 中上述的本地状态是用this.state和setState()来处理。但本地状态的状态管理在膨胀的应用中很快会变得混乱,因为:

  • 一个组件需要和另一个组件共享状态
  • 一个组件需要改变另一个组件的状态

到一定程度时,推算应用的状态将会变得越来越困难。它就会变成一个有很多状态对象并且在组件层级上互相修改状态的混乱应用。在大部分情况下,状态对象和状态的修改并没有必要绑定在一些组件上。当你把状态提升时,它们可以通过组件树得到。

所以,解决方案是引入状态管理库,比如:Mobx 或 Redux。它提供工具在某个地方保存状态、修改状态和更新状态。你可以从一个地方获得状态,一个地方修改它,一个地方得到它的更新。它遵循单一数据源的原则。这让我们更容易推断状态的值和状态的修改,因为它们与我们的组件是解耦的。

Flux

Flux 是一种架构思想。

Flux将一个应用分成四个部分。

  • View: 视图层
  • Action(动作):视图层发出的消息(比如mouseClick)
  • Dispatcher(派发器):用来接收Actions、执行回调函数
  • Store(数据层):用来存放应用的状态,一旦发生变动,就提醒Views要更新页面

Redux

Redux 是从 flux 架构派生出来的。

对比flux

  • 单一数据源:Flux 的数据源可以是多个。
  • State 是只读的:Flux 的 State 可以随便改。
  • 使用纯函数来执行修改state:Flux 执行修改的不一定是纯函数。

Redux 被 FP(函数式编程)原则所影响。既然 Redux 拥抱 FP,那它使用的就是纯函数。一个接受输入并返回输出并且没有其他依赖的纯函数。一个纯函数在相同的输入下输出总是相同而且没有任何副作用。

(state, action) => newState

你的 Redux state 是不可变的,你应该总是返回一个新的 state 而不是修改原 state。你不应该执行 state 的修改或依据对象引用的更改。

// don't do this in Redux, because it mutates the array
function addAuthor(state, action) {
 return state.authors.push(action.author);
}
// stay immutable and always return a new object
function addAuthor(state, action) {
 return [ ...state.authors, action.author ];
}

最后,在 Redux 的习惯用法里,state 的格式是像数据库一样标准化的。实体之间只靠 id 互相引用,这是最佳实践。虽然不是每个人都这样做,你也可以使用 normalizr 来使 state 标准化。标准化的 state 让你能够保持一个扁平的 state 和保持实体为单一数据源。

{
 post: {
 	id: 'a',
 	authorId: 'b',
 	...
 },
 author: {
 	id: 'b',
 	postIds: ['a', ...],
 	...
 }
}

Mobx

Mobx 则是受到面向对象编程和响应式编程的影响。它将 state 包装成可观察的对象,因此你的 state 就有了 Observable 的所有能力。state 数据可以只有普通的 setter 和 getter,但 observable 让我们能在数据改变的时候得到更新的值。

Mobx 的 state 是可变的,所以你直接的修改 state :

function addAuthor(author) {
 this.authors.push(author);
}

除此之外,state 实体保持嵌套的数据结构来互相关联。你不必标准化 state,而是让它们保持嵌套。

{
 post: {
 	id: 'a',
 	...
 	author: {
 		id: 'b',
 		...
 	}
 }
}

单 store 与多 stores

在 Redux 中,你将所有的 state 都放在一个全局的 store。这个 store 对象就是你的单一数据源。另一方面,多个 reducers 允许你修改不可变的 state。

Mobx 则相反,它使用多 stores。和 Redux 的 reducers 类似,你可以在技术层面或领域进行分治。也许你想在不同的 stores 里保存你的领域实体,但仍然保持对视图中 state 的控制。毕竟你配置 state 是为了让应用看起来更合理。

从技术层面来说,你一样可以在 Redux 中使用多个 stores。没有人强迫你只能只用一个 store。 但那不是 Redux 建议的用法。因为那违反了最佳实践。在 Redux 中,你的单 store 通过 reducers 的全局事件来响应更新。

使用异同

redux:

const initialState = {
    users: [
        {
            name: 'Dan'
        },
        {
            name: 'Michel'
        }
    ]
};
// reducer
function users(state = initialState, action) {
    switch (action.type) {
        case 'USER_ADD':
            return { ...state, users: [...state.users, action.user] };
        default:
            return state;
    }
}
// action
{ type: 'USER_ADD', user: user };

必须使用 dispatch({ type: 'USER_ADD', user: user });来为全局 state 添加一个新 user 。

mobx:

在 Mobx 中,一个 store 只管理一个子 state(就像 Redux 中管理子 state 的 reducer),但你可以直接修改 state 。

class UserStore {
    @observable users = [
        {
            name: 'Dan'
        },
        {
            name: 'Michel'
        }
    ];
    @action addUser = (user) => {
        this.users.push(user);
    }
}

可以调用 store 实例的方法:userStore.users.push(user);。这是一种最佳实践,虽然使用 actions 去操作 state 的修改更加清楚明确。在 Mobx 中你可以加上 useStrict() 来强制使用 action。

redux vs mobx:

你已经看到如何在 Redux 和 Mobx 中更新 state 。它们是不同的,Redux 中 state 是只读的,你只能使用明确的 actions 来修改 state ,Mobx 则相反,state 是可读和写的,你可以不使用 actions 直接修改 state,但你可以 useStrict() 来使用明确的 actions 。

在 Mobx 中你改变注解过的对象,组件就会更新。Mobx 比 Redux 使用了更多的内部魔法实现,因此在刚开始的时候只要更少的代码。有 Angular 背景的会觉得跟双向绑定很像。你在一个地方保存 state ,通过注解观察 state ,一旦 state 修改组件会自动的更新。

Redux 库非常小,大部分时间你都是在处理纯 JavaScript 对象和数组。它比 Mobx 更接近 vanilla JavaScript 。Mobx 通过包装对象和数组为可观察对象,从而隐藏了大部分的样板代码。它是建立在隐藏抽象之上的。感觉像是出现了魔法,但却很难理解其内在的机制。Redux 则可以简单通过纯 JavaScript 来推断。它使你的应用更简单的测试和调试。 另外,我们重新回到单页应用的最开始来考虑,一系列的单页应用框架和库面临着相同的状态管理问题,它最终被 flux 模式解决了。Redux 是这个模式的成功者。

Mobx 则又处在相反的方向。我们直接修改 state 而没有拥抱函数式编程的好处。对一些开发者来说,这让他们觉得像双向绑定。一段时间之后,由于没有引入类似 Redux 的状态管理库,他们可能又会陷入同样的问题。状态管理分散在各个组件,导致最后一团糟。

使用 Redux,你有一个既定的模式组织代码,而 Mobx 则无主张。但拥抱 Mobx 最佳实践会是明智的。 开发者需要知道如何组织状态管理从而更好的推断它。不然他们就会想要直接在组件中修改它。

总结:

redux单一store,mobx多个store;

redux函数式编程,mobx面向对象;

redux数据流更清晰,易追踪,mobx更新view更精准,颗粒度很细;

redux引入中间件处理副作用,mobx没有中间件,依靠autorunAsync之类的方法;

redux有架子模版,mobx更简洁自由;

但其实 Redux 和 MobX 并没有孰优孰劣,Redux 比 Mobx 更多的样板代码,是因为特定的设计约束。如果项目比较小的话,使用 MobX 会比较灵活,但是大型项目,像 MobX 这样没有约束,没有最佳实践的方式,会造成代码很难维护,各有利弊。

一般来说,小项目建议 MobX 就够了,大项目还是用 Redux 比较合适。

DVA

dva 首先是一个基于 redux redux-saga 的数据流方案,然后为了简化开发体验,dva 还额外内置了 react-router fetch ,所以也可以理解为一个轻量级的应用框架。

dva = React-Router + Redux + Redux-saga

特性

易学易用,仅有 6 个 api,对 redux 用户尤其友好,配合 umi 使用 后更是降低为 0 API

elm 概念,通过 reducers, effects 和 subscriptions 组织 model

插件机制,比如 dva-loading 可以自动处理 loading 状态,不用一遍遍地写 showLoading 和 hideLoading

支持 HMR,基于 babel-plugin-dva-hmr ,实现 components、routes 和 models 的 HMR

vuex

核心概念

state:单一状态树

getter:store的计算属性

mutation:同步事务更改store,类似redux的reducer

action:异步操作变更store

module:模块化store

vuex vs redux

都是flux的实现

vuex的流向: view——>commit——>mutations——>state变化——>view变化(同步操作) view——>dispatch——>actions——>mutations——>state变化——>view变化(异步操作)

redux的流向: view——>actions——>reducer——>state变化——>view变化(同步异步一样)

Vuex相对于Redux的不同点有: (1)改进了Redux中的Action和Reducer函数,以mutations变化函数取代Reducer, 无需switch,只需在对应的mutation函数里改变state值即可 (2)由于Vue自动重新渲染的特性,无需订阅重新渲染函数,只要生成新的State即可 (3)Vuex数据流的顺序是:View调用store.commit提交对应的请求到Store中对应的mutation函数->store改变(vue检测到数据变化自动渲染)