概述:把组件之间需要共享的状态抽取出来,遵循特定的约定,统一来管理,让状态的变化可以预测
Store 模式#
将状态存到一个外部变量, this.$root.$data
var store = {
state: {
message: 'Hello!'
},
setMessageAction (newValue) {
// 发生改变记录点日志啥的
this.state.message = newValue
},
clearMessageAction () {
this.state.message = ''
}
}
由于 store 的 state 值改变方式(mutation)只有通过触发 action 来操作,因此可以很容易跟踪到 state 的改变流程,出现错误也能通过日志明确错误位置
store并没有限制组件只能通过action来修改state
由这一点演化得出了 Flux 架构
Flux#
Flux 是类似于 MVC、MVVM 之类的一种思想,它把一个应用分成四个部分
——View、Action、Dispatcher、Store
View 视图层可以是通过 vue 或者 react 等框架实现,而 View 中的数据都是 Store,Store 改变就抛出一个事件,通知所有的订阅者(或者监听,不同的框架对应不同的数据响应技术)发生改变
Flux 要求,View 要想修改 Store,必须经过一套流程
- 视图先要告诉 Dispatcher,让 Dispatcher dispatch 一个 action
- Dispatcher 收到 View 发出的 action,然后转发给 Store
- Store 就触发相应的 action 来更新数据
- 数据更新则伴随着 View 的更新
注意:
- Dispatcher 的作用是接受所有的 Action。然后发给所有的 Store(Action 可能是 View 触发的,也可能是其他地方触发的,如测试用例)
- Store 的改变只能通过 Action,Store 不应该有公开的 Setter,所有的 Setter 都应该是私有的,只能有公开的 Getter
- 具体 Action 的处理逻辑一般放在 Store 里
Flux 特点: 单向流动
Redux#
与 Flux 思想类似,
但修改了 Flux 的一些特性:
- 一个应用可以拥有多个 Store
- 多个 Store 间可能存在依赖关系
- Store 还封装了处理数据的逻辑
Store#
Redux 里面只有一个 Store,整个应用的数据都在这个大 Store 里面。Store 的 State 不能直接修改,每次只能返回一个新的 State。Redux 整了一个 createStore 函数来生成 Store。
import { createStore } from 'redux';
const store = createStore(fn);
Store 允许使用 store.subscribe 方法设置监听函数,一旦 State 发生变化,就自动执行这个函数。这样不管 View 是用什么实现的,只要把 View 的更新函数 subscribe 一下,就可以实现 State 变化之后,View 自动渲染了。比如在 React 里,把组件的 render 方法或 setState 方法订阅进去就行。
Action#
和 Flux 一样,Redux 里面也有 Action,Action 就是 View 发出的通知,告诉 Store State 要改变。Action 必须有一个 type 属性,代表 Action 的名称,其他可以设置一堆属性,作为参数供 State 变更时参考。
const action = {
type: 'ADD_TODO',
payload: 'Learn Redux'
};
Redux 可以用 Action Creator 批量来生成一些 Action。
Reducer#
Redux 没有 Dispatcher 的概念,Store 里面已经集成了 dispatch 方法。store.dispatch () 是 View 发出 Action 的唯一方法。
import { createStore } from 'redux';
const store = createStore(fn);
store.dispatch({
type: 'ADD_TODO',
payload: 'Learn Redux'
});
Redux 用一个叫做 Reducer 的纯函数来处理事件。Store 收到 Action 以后,必须给出一个新的 State(就是刚才说的 Store 的 State 不能直接修改,每次只能返回一个新的 State),这样 View 才会发生变化。这种 State 的计算过程就叫做 Reduce
纯函数,即没有任何副作用
- 对于相同的输入,永远都只会有相同发输出
- 不会影响挖补的变量,也不会被外部变量影响
- 不能改写参数
Redux 根据应用的状态和当前的 action 推导出新的 state:(previousState, action) => newState
类比 Flux:(state, action) => state
question: 为什么叫做 Reducer 呢?
—— reduce 是一个函数式编程的概念,经常和 map 放在一起说,简单来说,map 就是映射,reduce 就是归纳。
映射就是把一个列表按照一定规则映射成另一个列表,而 reduce 是把一个列表通过一定规则进行合并,也可以理解为对初始值进行一系列的操作,返回一个新的值
整体流程#
1、用户通过 View 发出 Action:
store.dispatch(action);
2、然后 Store 自动调用 Reducer,并且传入两个参数:当前 State 和收到的 Action。 Reducer 会返回新的 State 。
let nextState = xxxReducer(previousState, action);
3、State 一旦有变化,Store 就会调用监听函数。
store.subscribe(listener);
4、listener 可以通过 store.getState () 得到当前状态。如果使用的是 React,这时可以触发重新渲染 View。
function listerner() {
let newState = store.getState();
component.setState(newState);
}
对比 Flux#
和 Flux 比较一下:Flux 中 Store 是各自为战的,每个 Store 只对对应的 View 负责,每次更新都只通知对应的 View:
Redux 中各子 Reducer 都是由根 Reducer 统一管理的,每个子 Reducer 的变化都要经过根 Reducer 的整合:
简单来说,Redux 有三大原则: 单一数据源:Flux 的数据源可以是多个。 State 是只读的:Flux 的 State 可以随便改。 * 使用纯函数来执行修改:Flux 执行修改的不一定是纯函数。
Redux 和 Flux 一样都是单向数据流
与 React 关系#
Redux 和 Flux 类似,只是一种思想或者规范,它和 React 之间没有关系。Redux 支持 React、Angular、Ember、jQuery 甚至纯 JavaScript。
但是因为 React 包含函数式的思想,也是单向数据流,和 Redux 很搭,所以一般都用 Redux 来进行状态管理。为了简单处理 Redux 和 React UI 的绑定,一般通过一个叫 react-redux 的库和 React 配合使用,这个是 react 官方出的(如果不用 react-redux,那么手动处理 Redux 和 UI 的绑定,需要写很多重复的代码,很容易出错,而且有很多 UI 渲染逻辑的优化不一定能处理好)。
Redux 将 React 组件分为容器型组件和展示型组件,容器型组件一般通过 connect 函数生成,它订阅了全局状态的变化,通过 mapStateToProps 函数,可以对全局状态进行过滤,而展示型组件不直接从 global state 获取数据,其数据来源于父组件。
如果一个组件既需要 UI 呈现,又需要业务逻辑处理,那就得拆,拆成一个容器组件包着一个展示组件。
Redux-saga#
Redux 处理异步操作,添加中间件后的产物
官方文档:Redux-Saga
Dva#
官方定义:dva 首先是一个基于 redux 和 redux-saga 的数据流方案,然后为了简化开发体验,dva 还额外内置了 react-router 和 fetch,所以也可以理解为一个轻量级的应用框架
简单理解,就是让使用 react-redux 和 redux-saga 编写的代码组织起来更合理,维护起来更方便
之前我们聊了 redux、react-redux、redux-saga 之类的概念,大家肯定觉得头昏脑涨的,什么 action、reducer、saga 之类的,写一个功能要在这些 js 文件里面不停的切换。dva 做的事情很简单,就是让这些东西可以写到一起,不用分开来写了
比如:
// 以前书写的方式是创建 sagas/products.js, reducers/products.js 和actions/products.js,然后把 saga、action、reducer 啥的分开来写,来回切换
app.model({
// namespace - 对应 reducer 在 combine 到 rootReducer 时的 key 值
namespace: 'products',
// state - 对应 reducer 的 initialState
state: {
list: [],
loading: false,
},
// subscription - 在 dom ready 后执行
subscriptions: [
function(dispatch) {
dispatch({type: 'products/query'});
},
],
// effects - 对应 saga,并简化了使用
effects: {
['products/query']: function*() {
yield call(delay(800));
yield put({
type: 'products/query/success',
payload: ['ant-tool', 'roof'],
});
},
},
// reducers - 就是传统的 reducers
reducers: {
['products/query'](state) {
return { ...state, loading: true, };
},
['products/query/success'](state, { payload }) {
return { ...state, loading: false, list: payload };
},
},
});
MobX#
官网:MobX 中文文档
对比 Flux 体系的单向数据流方案,Mobx 的思想则是 :任何源自应用状态的东西都应该自动地获得 —— 状态只要一变,其他用到状态的地方就都跟着自动变
Flux 或者说 Redux 的思想主要就是函数式编程(FP)的思想,所以学习起来会觉得累一些。而 MobX 更接近于面向对象编程,它把 state 包装成可观察的对象,这个对象会驱动各种改变。什么是可观察?就是 MobX 老大哥在看着 state 呢。state 只要一改变,所有用到它的地方就都跟着改变了。这样整个 View 可以被 state 来驱动。
const obj = observable({
a: 1,
b: 2
})
autoRun(() => {
console.log(obj.a)
})
obj.b = 3 // 什么都没有发生
obj.a = 2 // observe 函数的回调触发了,控制台输出:2
上面的 obj,他的 obj.a 属性被使用了,那么只要 obj.a 属性一变,所有使用的地方都会被调用。autoRun 就是这个老大哥,他看着所有依赖 obj.a 的地方,也就是收集所有对 obj.a 的依赖。当 obj.a 改变时,老大哥就会触发所有依赖去更新
MobX 和 Flux、Redux 一样,都是和具体的前端框架无关的,也就是说可以用于 React(mobx-react) 或者 Vue(mobx-vue)。一般来说,用到 React 比较常见,很少用于 Vue,因为 Vuex 本身就类似 MobX,很灵活。如果我们把 MobX 用于 React 或者 Vue,可以看到很多 setState () 和 this.state.xxx = 这样的处理都可以省了。