jiechen257

jiechen257

twitter
github

状态管理工具的演变

概述:把组件之间需要共享的状态抽取出来,遵循特定的约定,统一来管理,让状态的变化可以预测

Store 模式#

将状态存到一个外部变量, this.$root.$data

var store = {
  state: {
    message: 'Hello!'
  },
  setMessageAction (newValue) {
    // 发生改变记录点日志啥的
    this.state.message = newValue
  },
  clearMessageAction () {
    this.state.message = ''
  }
}

由于 store 的 state 值改变方式(mutation)只有通过触发 action 来操作,因此可以很容易跟踪到 state 的改变流程,出现错误也能通过日志明确错误位置

image

store并没有限制组件只能通过action来修改state
由这一点演化得出了 Flux 架构

Flux#

Flux 是类似于 MVC、MVVM 之类的一种思想,它把一个应用分成四个部分
——View、Action、Dispatcher、Store

image

View 视图层可以是通过 vue 或者 react 等框架实现,而 View 中的数据都是 Store,Store 改变就抛出一个事件,通知所有的订阅者(或者监听,不同的框架对应不同的数据响应技术)发生改变

Flux 要求,View 要想修改 Store,必须经过一套流程

  1. 视图先要告诉 Dispatcher,让 Dispatcher dispatch 一个 action
  2. Dispatcher 收到 View 发出的 action,然后转发给 Store
  3. Store 就触发相应的 action 来更新数据
  4. 数据更新则伴随着 View 的更新

注意:

  • Dispatcher 的作用是接受所有的 Action。然后发给所有的 Store(Action 可能是 View 触发的,也可能是其他地方触发的,如测试用例)
  • Store 的改变只能通过 Action,Store 不应该有公开的 Setter,所有的 Setter 都应该是私有的,只能有公开的 Getter
  • 具体 Action 的处理逻辑一般放在 Store 里

Flux 特点: 单向流动

Redux#

与 Flux 思想类似,
但修改了 Flux 的一些特性:

  • 一个应用可以拥有多个 Store
  • 多个 Store 间可能存在依赖关系
  • Store 还封装了处理数据的逻辑

image

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:

image

Redux 中各子 Reducer 都是由根 Reducer 统一管理的,每个子 Reducer 的变化都要经过根 Reducer 的整合:

image

简单来说,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 获取数据,其数据来源于父组件。

image

如果一个组件既需要 UI 呈现,又需要业务逻辑处理,那就得拆,拆成一个容器组件包着一个展示组件。

Redux-saga#

Redux 处理异步操作,添加中间件后的产物

官方文档:Redux-Saga

Dva#

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

简单理解,就是让使用 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 的思想则是 :任何源自应用状态的东西都应该自动地获得 —— 状态只要一变,其他用到状态的地方就都跟着自动变

image

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 = 这样的处理都可以省了。

参考#

Vuex、Flux、Redux、Redux-saga、Dva、MobX

加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。