flux/redux/mobx/vuex相关解析
状态管理是什么?
在组件化开发的新前端发展史上,组件的发展提供了更好的编码效率,更好的代码阅读性,维护性,补充HTML5语义化标签的不足。前端承担了越来越多的任务,特别是做一个spa项目。然而我们父子组件沟通可以通过props和回调,但是在两个组件我们并不知道它们的调用关系的时候,如何进行沟通呢?
所以:引入了状态管理的概念。比如在react中,通信解决方式有状态提升和发布、订阅,状态提升又分为container组件定义和使用context属性传递:
- container组件是说把两个组件需要共享的状态提升到一个共同的根组件上,通过 props 传递 state 以及 changeState 的方法。
- context属性传递是说在使用React.createContext()声明了context对象,这个Context对象包含两个组件,
和 。使用这个对象的provider包装需要共用这个context的父组件,把要传递的值通过value属性传递给子组件;使用这个对象的consumer包装要使用context属性的子组件。
1 | const ThemeContext = React.createContext({ |
它会使组件更新更加困难。这个provider有更新,它的子代所有组件会重新渲染。不受shouldComponentUpdate方法约束,更改是通过使用与Object.is相同的算法比较新值和旧值来确定的。
旧版本的Context的更新需要依赖setState(),是不可靠的,不过这个问题在新版的API中得以解决。
但如果开发组件过程中可以确保组件的内聚性,可控可维护,不破坏组件树的依赖关系,影响范围小,可以考虑使用Context解决一些问题。
context运用于react-router: https://www.jianshu.com/p/eba2b76b290b
如果组件的功能不能单靠组件自身来完成,还需要依赖额外的子组件,那么可以利用Context构建一个由多个子组件组合的组件。为了让相关的子组件一同发挥作用,react-router的实现方案是利用Context在<Router />
、<Link />
以及<Route />
这些相关的组件之间共享一个router,进而完成路由的统一操作和管理。<Router />
的核心就是为子组件提供一个带有router属性的Context,同时监听history,一旦history发生变化,便通过setState()触发组件重新渲染。<Link />
的核心就是渲染<a>
标签,拦截<a>
标签的点击事件,然后通过<Router />
共享的router对history进行路由操作,进而通知<Router />
重新渲染。<Route />
有一部分源码与<Router />
相似,可以实现路由的嵌套,但其核心是通过Context共享的router,判断是否匹配当前路由的路径,然后渲染组件。
- 发布订阅就是一个组件订阅,一个组件发布。
因此,出现了独立管理状态的地方。可以实现数据的订阅、发布。如flux/redux/vuex/mobx。他们有什么区别呢?
一句话总结:
它们都是基于单向数据流的状态管理方法论。Flux最早提出,作为对传统前端MVC的一种改进(我不认为是颠覆)。Redux深受Flux的启发,又加入了函数式编程的思想,算是Flux的极大增强版本。Vuex可以说是基于Flux并且吸收了Redux的一些特点,但它与Vue是紧密捆绑的。Redux其实除了在React中广泛应用。
flux
![](/images/vuexandredux_02.png)
View: 确定相应的Store以及监听其变化来更新视图。发起Action。
Action:每个Action都是一个对象,包含一个actionType属性(说明动作的类型)和一些其他属性(用来传递数据)
Dispatcher:全局唯一。逻辑简单,只用来派发action去相应的store。通过 AppDispatcher.register() 来登记各种Action的回调函数。
Store: 存放view中的数据。发送change事件,通过view中定义的handler捕捉变化。
1 | //store |
可以看出,flux更新逻辑在store。dispatcher只是将action进行处理,然后调用store里面的方法去更新数据。
redux
![](/images/vuexandredux_03.png)
Redux = Reducer + Flux
- Redux将Flux中的Dispatcher并入了Store。也可以理解为Redux没Dispatcher。Redux的设想是用户永远不会变动数据,应该在reducer中返回新的对象来作为应用的新状态。
- Redux增加了Reducer.
注:通过代码对比的直观感受就是Flux中的view需要知道具体对应哪个store。而在Redux中,store成为一个被所有view共享的公共对象,view只需要通过store.dispatch()来发送action,无需关心具体处理函数。
View: 通过全局唯一的store dispatch action 以及获取最新state
Action: 与flux一致。
reducer: 是current state 和 action 为参数计算new state的纯函数。
Store: 全局唯一。Dispatcher功能已被整合进store:store.dispatch()。state 一旦有变化,store 就会调用通过store.subscribe()注册的回调函数(一般是render)。
1 | const createStore = (reducer) => { |
[三大原则,store 唯一,state 只读, reducer 纯函数。]
所以实现我们大部分使用:redux + react-redux + redux-chunk + redux-immutable 。用来与react框架进行交互。redux的dispatch默认只能传输action, 引用redux-thunk中间件,可以让action创建函数先不返回一个action对象,而是返回一个函数,函数传递两个参数(dispatch,getState),在函数体内进行业务逻辑的封装。通过使用指定的 middleware,action 创建函数除了返回 action 对象外还可以返回函数。以此来让你 dispatch 一些除了 action 以外的其他内容,例如:函数或者 Promise。
1 | const mapStateToProps = state => { |
React Redux 组件
容器组件就是使用 store.subscribe() 从 Redux state 树中读取部分数据,并通过 props 来把这些数据提供给要渲染的组件。
你可以手工来开发容器组件,但建议使用 React Redux 库的 connect() 方法来生成,这个方法做了性能优化来避免很多不必要的重复渲染。
connect 出来的 HOC, 通过 Provider 提供的 context 上的 store,在内部向 store subscribe 了 onStateChange 事件。只要派发了 action,就会触发一次 onStateChange 事件,HOC 就能感知 store 的更新再根据 onStateChange 的结果决定是否要 update。
vuex
只用来读取的状态集中放在store中;改变状态的方式是提交mutations,这是个同步的事物;异步逻辑应该封装在action中。
state
Vuex 使用单一状态树,即每个应用将仅仅包含一个store 实例,但单一状态树和模块化并不冲突。存放的数据状态,不可以直接修改里面的数据。
mutations
mutations定义的方法动态修改Vuex 的 store 中的状态或数据。mutation 必须是同步函数。
getters
类似vue的计算属性,主要用来过滤一些数据。
action
actions可以理解为通过将mutations里面处理数据的方法变成可异步的处理数据的方法,简单的说就是异步操作数据。view 层通过 store.dispath 来分发 action。
1 | const store = new Vuex.Store(storeSettings) |
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块
局部模块:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36const moduleA = {
state: { count: 0 },
mutations: {
increment (state) {
// 这里的 `state` 对象是模块的局部状态
state.count++
}
},
getters: {
//根节点状态会作为第三个参数暴露出来
doubleCount (state, getters, rootState) {
return state.count * 2
}
},
actions: {
//局部状态通过 context.state 暴露出来,根节点状态则为 context.rootState
incrementIfOddOnRootSum ({ state, commit, rootState }) {
if ((state.count + rootState.count) % 2 === 1) {
commit('increment')
}
}
}
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
store.state.a // -> moduleA 的状态
this.$store.dispatch('a/incrementIfOddOnRootSum')
使用mutation来替换redux中的reducer
Vuex有自动渲染的功能,所以无需要专门监听state。
Vuex中的action是一个函数集合对象,用于async/sync commit mutaions. 和Redux或者Flux中的action只是简单对象有本质不同,只是叫了一个相同名字。
mobx
通过observable观察某一个变量,当该变量产生变化时,对应的autorun内的回调函数就会发生变化。 Observable 、Computed 依赖state产生 obserable 、Autonrun、Action、Observer,
1 | import {observable, autorun} from 'mobx'; |
Action:定义改变状态的动作函数,包括如何变更状态;
Store:集中管理模块状态(State)和动作(action);
Derivation(衍生):从应用状态中派生而出,且没有任何其他影响的数据,我们称为derivation(衍生),衍生在以下情况下存在:
用户界面;
衍生数据;
衍生主要有两种:
Computed Values(计算值):计算值总是可以使用纯函数(pure function)从当前可观察状态中获取;
Reactions(反应):反应指状态变更时需要自动发生的副作用,这种情况下,我们需要实现其读写操作;
react-mobx而言,同样需要两个步骤:
Provider:使用mobx-react提供的Provider将所有stores注入应用;
使用inject将特定store注入某组件,store可以传递状态或action;然后使用observer保证组件能响应store中的可观察对象(observable)变更,即store更新,组件视图响应式更新。
mobx 和 redux 比较
- redux对象通常不可变(不能直接操作状态对象,而总是在原来状态对象基础上返回一个新的状态对象,这样就能很方便的返回应用上一状态)。而Mobx中可以直接使用新值更新状态对象。
- redux单一store,mobx多个
- redux函数式编程,mobx面对对象
- Redux需要手动追踪所有状态对象的变更,Redux需要手动追踪所有状态对象的变更
- 运用到react上,mobx-react和react-redux