前端状态管理与有限状态机

当下前端流行的框架,都是用状态来描述界面(state => view),能够说前端开发实际上就是在维护各类状态(state),这已经成为目前前端开发的共识。javascript

View = ViewModel(Model);
复制代码

理想状况下,ViewModel 是纯函数,给定相同的 Model,产出相同的 View。前端

state => view 很好理解,但如何在 view 中合理地修改 state 也是一个问题。vue

为何须要状态管理

举个例子

图书馆的管理,原来是开放式的,全部人能够随意进出书库借书还书,若是人数很少,这种方式能够减小流程,增长效率,一旦人数变多就势必形成混乱。java

Flux 就像是给这个图书馆加上了一个管理员,全部借书还书的行为都须要委托管理员去作,管理员会规范对书库的操做行为,也会记录每一个人的操做,减小混乱的现象。react

一个比喻

咱们寄一件东西的过程git

没有快递时:github

  • 打包准备好要送出去的东西
  • 直接到朋友家,把东西送给朋友
  • 很直接很方便,很费时间

有了快递公司:vuex

  • 打包准备好要送出去的东西
  • 到快递公司,填写物品,收件人等基本信息
  • 快递公司替你送物品到你的朋友家,咱们的工做结束了

多了快递公司,让快递公司给咱们送快递。数据库

当咱们只寄送物品给一个朋友,次数较少,物品又较少的时候,咱们直接去朋友家就挺好的。但当咱们要频繁寄送给不少朋友不少商品的时候,问题就复杂了。编程

软件工程的本质便是管理复杂度。使用状态管理类框架会有必定的学习成本并且一般会把简单的事情作复杂,但若是咱们想作复杂一点的事情(同时寄不少物品到多个不一样地址),对咱们来讲,快递会让复杂的事情变的简单。

这同时也解释了,是否须要添加状态管理框架,咱们能够根据本身的业务实际状况和技术团队的偏好而添加,有些状况下,建立一个全局对象就能解决不少问题。

核心思想

Flux 的核心思想:数据单向流动。

  • 不一样组件的 state,存放在一个外部的、公共的 Store 上面。
  • 组件订阅 Store 的不一样部分。
  • 组件发送(dispatch)动做(action),引起 Store 的更新。

Redux 的核心概念

  • 全部的状态存放在 Store。组件每次从新渲染,都必须由状态变化引发。
  • 用户在 UI 上发出 action。
  • reducer 函数接收 action,而后根据当前的 state,计算出新的 state。

Redux store 是单一数据源。Redux 没有 dispatcher 的概念,转而使用纯函数(pure function)代替。

Redux store 是不可变的(Immutable)。

MobX

  • Observable:它的 state 是可被观察的,不管是基本数据类型仍是引用数据类型,均可以使用 MobX 的 (@)observable 来转变为 observable value。
  • Reactions:它包含不一样的概念,基于被观察数据的更新致使某个计算值(computed values),或者是发送网络请求以及更新视图等,都属于响应的范畴,这也是响应式编程(Reactive Programming)在 JavaScript 中的一个应用。
  • Actions:它至关于全部响应的源头,例如用户在视图上的操做,或是某个网络请求的响应致使的被观察数据的变动。

和 Redux 对单向数据流的严格规范不一样,Mobx 只专一于从 store 到 view 的过程。在 Redux 中,数据的变动须要监听,而 Mobx 的数据依赖是基于运行时的,这点和 Vuex 更为接近。

Vuex 的状态管理模式

  • state,驱动应用的数据源;
  • view,以声明方式将 state 映射到视图;
  • actions,响应在 view 上的用户输入致使的状态变化。

Flux

Facebook 提出了 Flux 架构思想,规范了数据在应用中的流动方式。其基本架构以下入所示,其核心理念是单向数据流,它完善了 React 对应用状态的管理。

上图描述了页面的启动和运行原理:

1.经过 dispatcher 派发 action,并利用 store 中的 action 处理逻辑更新状态和 view

2.而 view 也能够触发新的 action,从而进入新的步骤 1

其中的 action 是用于描述动做的简单对象,一般经过用户对 view 的操做产生,包括动做类型和动做所携带的所需参数,好比描述删除列表项的 action:

{
    type: types.DELETE_ITEM,
    id: id
};
复制代码

而 dispatcher 用于对 action 进行分发,分发的目标就是注册在 store 里的事件处理函数:

dispatcher.register(function(action) {
  switch (action.type) {
    case "DELETE_ITEM":
      sotre.deleteItem(action.id); //更新状态
      store.emitItemDeleted(); //通知视图更新
      break;
    default:
    // no op
  }
});
复制代码

store 包含了应用的全部状态和逻辑,它有点像传统的 MVC 模型中的 model 层,但又与之有明显的区别,store 包括的是一个应用特定功能的所有状态和逻辑,它表明了应用的整个逻辑层;而不是像 Model 同样包含的是数据库中的一些记录和与之对应的逻辑。

参考连接:flux

Redux

原生 Redux API 最简单的用例

function counter(state, action) {
  if (typeof state === "undefined") {
    return 0;
  }

  switch (action.type) {
    case "INCREMENT":
      return state + 1;
    case "DECREMENT":
      return state - 1;
    default:
      return state;
  }
}

var store = Redux.createStore(counter); //
var valueEl = document.getElementById("value");

function render() {
  valueEl.innerHTML = store.getState().toString();
}

render();
store.subscribe(render);

document.getElementById("increment").addEventListener("click", function() {
  store.dispatch({ type: "INCREMENT" });
});

document.getElementById("decrement").addEventListener("click", function() {
  store.dispatch({ type: "DECREMENT" });
});

document.getElementById("incrementIfOdd").addEventListener("click", function() {
  if (store.getState() % 2 !== 0) {
    store.dispatch({ type: "INCREMENT" });
  }
});

document.getElementById("incrementAsync").addEventListener("click", function() {
  setTimeout(function() {
    store.dispatch({ type: "INCREMENT" });
  }, 1000);
});
复制代码

应用中全部的 state 都以一个对象树的形式储存在一个单一的 store 中。 改变 state 的惟一办法是触发 action,一个描述发生什么的对象。 为了描述 action 如何改变 state 树,你须要编写 reducers。

Redux 三大原则

  • 单一数据源

    整个应用的 state 被储存在一棵 object tree 中,而且这个 object tree 只存在于惟一一个 store 中。

  • state 是只读的

    惟一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象。

  • 使用纯函数来执行修改

    为了描述 action 如何改变 state tree ,你须要编写 reducers。 改变 state 的唯一方法是 dispatch action。你也能够 subscribe 监听 state 的变化,而后更新 UI。

严格的单向数据流是 Redux 架构的设计核心。

Redux API

Redux 的 API 很是少。

Redux 定义了一系列的约定(contract)来让你来实现(例如 reducers),同时提供少许辅助函数来把这些约定整合到一块儿。

Redux 只关心如何管理 state。在实际的项目中,你还须要使用 UI 绑定库如 react-redux。

  • createStore(reducer, [preloadedState], [enhancer])
  • combineReducers(reducers)
  • bindActionCreators(actionCreators, dispatch)
  • applyMiddleware(...middlewares)
  • compose(...functions)

immutable

在写 redux 的 action 的时候,老是须要用到扩展语句或者 Object.assign()的方式来获得一个新的 state,这一点对于 JavaScript 而言是对象的浅拷贝,它对内存的开销确定是大于 mobX 中那样直接操做对象属性的方式大得多。

参考连接:redux-immutable seamless-immutable reselect 为何使用 Redux 管理状态是可预测的

redux-saga

redux 是 react 技术栈中的状态控制流框架,使用了标准的函数式思想,指望(强制)全部状态管理都是纯函数。这也意味着各状态之间都是独立的。可是有一类状态 redux 直接甩给了的第三方模块,反作用模块 redux-saga 也就成了不辞辛苦的典型表明。反作用正是由于不肯定性和可变性而得名,而其给出的状态又是相互影响,如何解耦使得本来复杂的非线性呈现为线性逻辑,正是有限状态机的用武之处。

DvaJS

dva 首先是一个基于 reduxredux-saga 的数据流方案,而后为了简化开发体验,dva 还额外内置了 react-routerfetch,因此也能够理解为一个轻量级的应用框架。

在 redux 的生态圈内,每一个环节有多种方案,好比 Data 能够是 immutable 或者 plain object,在你选了 immutable 以后,用 immutable.js 仍是 seamless-immutable,以及是否用 redux-immutable 来辅助数据修改,都须要选择。

参考连接:Redux 中文文档 immutable-js immer dvajs React + Redux 最佳实践

MobX

MobX 是一个用法简单优雅、同时具备可扩展性的状态管理库。

一个简单的例子

import { observable, autorun } from "mobx";

const appState = observable({
  counter: 0,
  add(value) {
    this.counter += value;
  }
});

autorun(() => console.log(appState.counter));

setInterval(() => appState.add(1), 1000);
复制代码

在 mobx 中咱们能够直接修改状态

import { observable } from "mobx";

const appState = observable({ counter: 0 });

appState.counter += 1;
复制代码

能够经过引入 Strict 模式来避免这种不良好的实践:

import { useStrict } from "mobx";

useStrict(true);
复制代码

MobX 脱胎于响应式编程(Reactive Programming),其核心思想为 Anything that can be derived from the application state, should be derived. Automatically,即避免任何的重复状态。

MobX 中核心的概念便是 Observable,相信接触过响应式编程的确定很是熟悉,从后端的典型表明 RxJava 到 Android/iOS 开发中的各类响应式框架都各领风骚。

与 Redux 状态管理上的异同

Redux / MobX 均为客户端开源状态管理库,用状态来描述 UI 界面,它们与 React 都不具备强绑定关系,你也能够配合别的框架来使用它们。 固然,与 React 是再合适不过的了,React 做为 View 层的框架,经过 Virtual DOM 机制来优化 UI 渲染,Redux / MobX 则提供了将相应状态同步到 React 的机制。

Redux 与 MobX 的不一样主要集中于如下几点:

  • Redux 是单一数据源,而 MobX 每每是多个 store。MobX 能够根据应用的 UI、数据或业务逻辑来组织 store,具体如何进行须要你本身进行权衡。
  • Redux store 使用普通的 JavaScript 对象结构,MobX 将常规 JavaScript 对象包裹,赋予 observable 的能力,经过隐式订阅,自动跟踪 observable 的变化。MobX 是观察引用的,在跟踪函数中(例如:computed value、reactions 等等),任何被引用的 observable 的属性都会被记录,一旦引用改变,MobX 将做出反应。注意,不在跟踪函数中的属性将不会被跟踪,在异步中访问的属性也不会被跟踪。
  • Redux 的 state 是只读的,只能经过将以前的 state 与触发的 action 结合,产生新的 state,所以是纯净的(pure)。而 MobX 的 state 便可读又可写,action 是非必须的,能够直接赋值改变,所以是不纯净的(Impure)。
  • Redux 须要你去规范化你的 state,Immutable 数据使 Reducer 在更新时须要将状态树的祖先数据进行复制和更新,新的对象会致使与之 connect 的全部 UI 组件都重复渲染。所以 Redux state 不建议进行深层嵌套,或者须要咱们在组件中用 shouldComponentUpdate 优化。而 MobX 只自动更新你所关心的,没必要担忧嵌套带来的重渲染问题。
  • 在 Redux 中区分有 smart 组件与 dumb 组件,dumb 负责展现,smart 负责状态更新,数据获取。而在 MobX 中无需区分,都是 smart,当组件自身依赖的 observable 发生变化时,会做出响应。

Mobx 思想的实现原理

Mobx 最关键的函数在于 autoRun,autoRun 的专业名词叫作依赖收集,也就是经过天然的使用,来收集依赖,当变量改变时,根据收集的依赖来判断是否须要更新。Mobx 使用了 Object.defineProperty 拦截 getter 和 setter,和 Vue 同样。

参考连接: mobx MobX 中文文档

Vuex

Vuex 是专门为 Vue.js 设计的状态管理库。把组件的共享状态抽取出来,以一个全局单例模式管理。它采用集中式存储管理应用的全部组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

核心概念

  • State
  • Getter
  • Mutation
  • Action
  • Module

API

Vuex 的用法很简单,  一句话总结:commit mutation,dispatch action

参考连接:Vuex 官方文档

有限状态机(FSM)

有限状态机(finite-state machine)又称有限状态自动机,简称状态机,是表示有限个状态以及在这些状态之间的转移和动做等行为的数学模型,很是有用,能够模拟世界上大部分事物。

有限状态机并非一个复杂的概念,简单说,它有三个特征:

  • 状态总数(state)是有限的。
  • 任一时刻,只处在一种状态之中。
  • 某种条件下,会从一种状态转变(transition)到另外一种状态。

总结

使用状态去影响视图,而 Action 主要负责完成状态间的变动。代码如何更好的构建其核心在于使用最合理的状态去管理界面,并用最合理的动做去实现状态间的变动。

所谓的状态管理,实际上就是使用有限状态机来管理前端状态。

有限状态机对 JavaScript 的意义在于,不少对象能够写成有限状态机。

写代码以前,思考一下:

  • 页面有几种状态(初始化状态?成功状态?失败状态?出错状态?)。
  • 描述这些状态须要什么参数。
  • 在何时转变状态,须要改变哪些部分。

而后跟着思路,完成数据与 UI 部分。

参考连接:javascript-state-machine xstate managing-state-in-javascript-with-state-machines-stent

相关文章
相关标签/搜索