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

这篇文章试着聊邃晓这一堆看起来挺庞杂的东西。在聊之前,人人要一直记得一句话:统统前端观点,都是纸老虎

不论是Vue,照样 React,都须要治理状况(state),比方组件之间都有同享状况的须要。什么是同享状况?比方一个组件须要运用另一个组件的状况,或许一个组件须要转变另一个组件的状况,都是同享状况。

父子组件之间,兄弟组件之间同享状况,每每须要写许多没有必要的代码,比方把状况提升到父组件里,或许给兄弟组件写一个父组件,听听就以为挺烦琐。

假如不对状况举行有用的治理,状况在什么时候,由于什么原因,怎样变化就会不受掌握,就很难跟踪和测试了。假如没有经历过这方面的搅扰,可以简朴明白为会搞得很乱就对了

在软件开辟里,有些通用的头脑,比方断绝变化,商定优于设置等,断绝变化就是说做好笼统,把一些轻易变化的处所找到共性,断绝出来,不要去影响其他的代码。商定优于设置就是许多东西我们不一定要写一大堆的设置,比方我们几个人商定,view 文件夹里只能放视图,不能放过滤器,过滤器必需放到 filter 文件夹里,那这就是一种商定,商定好今后,我们就不必写一大堆设置文件了,我们要找一切的视图,直接从 view 文件夹里找就行。

依据这些头脑,关于状况治理的处置惩罚思绪就是:把组件之间须要同享的状况抽取出来,遵照特定的商定,一致来治理,让状况的变化可以展望。依据这个思绪,发生了许多的情势和库,我们来挨个聊聊。

Store 情势

最简朴的处置惩罚就是把状况存到一个外部变量内里,比方:this.$root.$data,固然也可以是一个全局变量。然则如许有一个题目,就是数据转变后,不会留下变动过的纪录,如许不利于调试。

所以我们轻微搞得庞杂一点,用一个简朴的 Store 情势:

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

store 的 state 来存数据,store 内里有一堆的 action,这些 action 来掌握 state 的转变,也就是不直接去对 state 做转变,而是经由历程 action 来转变,由于都走 action,我们就可以晓得究竟转变(mutation)是怎样被触发的,涌现毛病,也可以纪录纪录日记啥的。

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

不过这里没有限定组件内里不能修正 store 内里的 state,万一组件瞎胡修正,不经由历程 action,那我们也没法跟踪这些修正是怎样发作的。所以就须要划定一下,组件不许可直接修正属于 store 实例的 state,组件必需经由历程 action 来转变 state,也就是说,组件内里应当实行 action 来分发 (dispatch) 事宜关照 store 去转变。如许商定的优点是,我们可以纪录一切 store 中发作的 state 转变,同时完成能做到纪录变动 (mutation)、保留状况快照、汗青回滚/时候游览的先进的调试东西。

如许进化了一下,一个简朴的 Flux 架构就完成了。

Flux

Flux现实上是一种头脑,就像MVC,MVVM之类的,他给出了一些基础观点,一切的框架都可以依据他的头脑来做一些完成。

Flux把一个运用分成了4个部份:

  • View
  • Action
  • Dispatcher
  • Store

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

比方我们搞一个运用,不言而喻,这个运用内里会有一堆的 View,这个 View 可以是Vue的,也可以是 React的,啥框架都行,啥手艺都行。

View 一定是要展示数据的,所谓的数据,就是 Store,Store 很轻易邃晓,就是存数据的处所。固然我们可以把 Store 都放到一同,也可以离开来放,所以就有一堆的 Store。然则这些 View 都有一个特性,就是 Store 变了得随着变。

View 怎样随着变呢?平常 Store 一旦发作转变,都邑往外面发送一个事宜,比方 change,关照一切的定阅者。View 经由历程定阅也好,监听也好,差别的框架有差别的手艺,横竖 Store 变了,View 就会变。

View 不是光用来看的,平常都邑有用户操纵,用户点个按钮,改个表单啥的,就须要修正 Store。Flux 要求,View 要想修正 Store,必需经由一套流程,有点像我们适才 Store 情势内里说的那样。视图先要通知 Dispatcher,让 Dispatcher dispatch 一个 action,Dispatcher 就像是其中转站,收到 View 发出的 action,然后转发给 Store。比方新建一个用户,View 会发出一个叫 addUser 的 action 经由历程 Dispatcher 来转发,Dispatcher 会把 addUser 这个 action 发给一切的 store,store 就会触发 addUser 这个 action,来更新数据。数据一更新,那末 View 也就随着更新了。

这个历程有几个须要注重的点:

  • Dispatcher 的作用是吸收一切的 Action,然后发给一切的 Store。这里的 Action 多是 View 触发的,也有多是其他处所触发的,比方测试用例。转发的话也不是转发给某个 Store,而是一切 Store。
  • Store 的转变只能经由历程 Action,不能经由历程其他体式格局。也就是说 Store 不该当有公然的 Setter,一切 Setter 都应当是私有的,只能有公然的 Getter。细致 Action 的处置惩罚逻辑平常放在 Store 里。

听听形貌看看图,可以发明,Flux的最大特性就是数据都是单向活动的。

Redux

Flux 有一些瑕玷(特性),比方一个运用可以具有多个 Store,多个Store之间能够有依托关联;Store 封装了数据另有处置惩罚数据的逻辑。

所以人人在运用的时候,平常会用 Redux,他和 Flux 头脑比较相似,也有差别。

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

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 的盘算历程就叫做 Reducer。

什么是纯函数呢,就是说没有任何的副作用,比方如许一个函数:

function getAge(user) {
  user.age = user.age + 1;
  return user.age;
}

这个函数就有副作用,每一次雷同的输入,都能够致使差别的输出,而且还会影响输入 user 的值,再比方:

let b = 10;
function compare(a) {
  return a >= b;
}

这个函数也有副作用,就是依托外部的环境,b 在别处被转变了,返回值关于雷同的 a 就有能够不一样。

而 Reducer 是一个纯函数,关于雷同的输入,永久都只会有雷同的输出,不会影响外部的变量,也不会被外部变量影响,不得改写参数。它的作用也许就是如许,依据运用的状况和当前的 action 推导出新的 state:

(previousState, action) => newState

类比 Flux,Flux 有些像:

 (state, action) => state

为何叫做 Reducer 呢?reduce  是一个函数式编程的观点,常常和  map  放在一同说,简朴来讲,map  就是映照,reduce  就是归结。映照就是把一个列表根据一定划定规矩映照成另一个列表,而 reduce 是把一个列表经由历程一定划定规矩举行兼并,也可以明白为对初始值举行一系列的操纵,返回一个新的值。

比方  Array 就有一个要领叫 reduce,Array.prototype.reduce(reducer, ?initialValue),把 Array 整吧整吧弄成一个  newValue。

const array1 = [1, 2, 3, 4];
const reducer = (accumulator, currentValue) => accumulator + currentValue;

// 1 + 2 + 3 + 4
console.log(array1.reduce(reducer));
// expected output: 10

// 5 + 1 + 2 + 3 + 4
console.log(array1.reduce(reducer, 5));
// expected output: 15

看起来和 Redux 的 Reducer 是不是是彷佛彷佛,Redux 的 Reducer 就是 reduce 一个列表(action的列表)和一个 initialValue(初始的  State)到一个新的 value(新的  State)。

把上面的观点连起来,举个例子:

下面的代码声清楚明了 reducer:

const defaultState = 0;
const reducer = (state = defaultState, action) => {
  switch (action.type) {
    case 'ADD':
      return state + action.payload;
    default: 
      return state;
  }
};

createStore接收 Reducer 作为参数,天生一个新的 Store。今后每当store.dispatch发送过来一个新的 Action,就会自动挪用 Reducer,取得新的 State。

import { createStore } from 'redux';
const store = createStore(reducer);

createStore 内部干了什么事儿呢?经由历程一个简朴的 createStore 的完成,可以相识也许的道理(可以略过不看):

const createStore = (reducer) => {
  let state;
  let listeners = [];

  const getState = () => state;

  const dispatch = (action) => {
    state = reducer(state, action);
    listeners.forEach(listener => listener());
  };

  const subscribe = (listener) => {
    listeners.push(listener);
    return () => {
      listeners = listeners.filter(l => l !== listener);
    }
  };

  dispatch({});

  return { getState, dispatch, subscribe };
};

Redux 有许多的 Reducer,关于大型运用来讲,State 必定非常巨大,致使 Reducer 函数也非常巨大,所以须要做拆分。Redux  里每一个 Reducer 担任保护 State 树内里的一部份数据,多个 Reducer 可以经由历程 combineReducers 要领合成一个根 Reducer,这个根 Reducer 担任保护悉数 State。

import { combineReducers } from 'redux';

// 注重这类简写情势,State 的属性名必需与子 Reducer 同名
const chatReducer = combineReducers({
  Reducer1,
  Reducer2,
  Reducer3
})

combineReducers 干了什么事儿呢?经由历程简朴的 combineReducers 的完成,可以相识也许的道理(可以略过不看):

const combineReducers = reducers => {
  return (state = {}, action) => {
    return Object.keys(reducers).reduce(
      (nextState, key) => {
        nextState[key] = reducers[key](state[key], action);
        return nextState;
      },
      {} 
    );
  };
};

流程

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

再回忆一下适才的流程图,尝试走一遍  Redux  流程:

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:

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

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

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

简朴来讲,Redux有三大原则:

  • 单一数据源:Flux 的数据源可以是多个。
  • State 是只读的:Flux 的 State 可以随意改。
  • 运用纯函数来实行修正:Flux 实行修正的不一定是纯函数。

Redux 和 Flux 一样都是单向数据流

中间件

适才说到的都是比较抱负的同步状况。在现实项目中,平常都邑有同步和异步操纵,所以 Flux、Redux 之类的头脑,终究都要落地到同步异步的处置惩罚中来。

在  Redux  中,同步的表现就是:Action 发出今后,Reducer 马上算出 State。那末异步的表现就是:Action 发出今后,过一段时候再实行 Reducer。

那怎样才 Reducer 在异步操纵终了后自动实行呢?Redux 引入了中间件 Middleware 的观点。

实在我们从新回忆一下适才的流程,可以发明每一个步骤都很地道,都不太适宜到场异步的操纵,比方 Reducer,纯函数,一定不能负担异步操纵,那样会被外部IO滋扰。Action呢,就是一个纯对象,放不了操纵。那想来想去,只能在 View 里发送 Action 的时候,加上一些异步操纵了。比方下面的代码,给原本的  dispatch  要领包裹了一层,加上了一些日记打印的功用:

let next = store.dispatch;
store.dispatch = function dispatchAndLog(action) {
  console.log('dispatching', action);
  next(action);
  console.log('next state', store.getState());
}

既然能加日记打印,固然也能到场异步操纵。所以中间件简朴来讲,就是对 store.dispatch 要领举行一些革新的函数。不睁开说了,所以假如想细致相识中间件,可以点这里

Redux 供应了一个 applyMiddleware 要领来运用中间件:

const store = createStore(
  reducer,
  applyMiddleware(thunk, promise, logger)
);

这个要领重要就是把一切的中间件构成一个数组,顺次实行。也就是说,任何被发送到 store 的 action 如今都邑经由thunk,promise,logger 这几其中间件了。

处置惩罚异步

关于异步操纵来讲,有两个非常症结的时候:提议要求的时候,和吸收到相应的时候(能够胜利,也能够失利或许超时),这两个时候都能够会变动运用的 state。平常是如许一个历程:

  1. 要求最先时,dispatch  一个要求最先 Action,触发 State 更新为“正在要求”状况,View 从新衬着,比方展示个Loading啥的。
  2. 要求终了后,假如胜利,dispatch  一个要求胜利 Action,隐蔽掉  Loading,把新的数据更新到  State;假如失利,dispatch  一个要求失利 Action,隐蔽掉  Loading,给个失利提醒。

明显,用  Redux  处置惩罚异步,可以自身写中间件来处置惩罚,固然大多数人会挑选一些现成的支撑异步处置惩罚的中间件。比方 redux-thunk 或 redux-promise 。

Redux-thunk

thunk 比较简朴,没有做太多的封装,把大部份自主权交给了用户:

const createFetchDataAction = function(id) {
    return function(dispatch, getState) {
        // 最先要求,dispatch 一个 FETCH_DATA_START action
        dispatch({
            type: FETCH_DATA_START, 
            payload: id
        })
        api.fetchData(id) 
            .then(response => {
                // 要求胜利,dispatch 一个 FETCH_DATA_SUCCESS action
                dispatch({
                    type: FETCH_DATA_SUCCESS,
                    payload: response
                })
            })
            .catch(error => {
                // 要求失利,dispatch 一个 FETCH_DATA_FAILED action   
                dispatch({
                    type: FETCH_DATA_FAILED,
                    payload: error
                })
            }) 
    }
}

//reducer
const reducer = function(oldState, action) {
    switch(action.type) {
    case FETCH_DATA_START : 
        // 处置惩罚 loading 等
    case FETCH_DATA_SUCCESS : 
        // 更新 store 等
    case FETCH_DATA_FAILED : 
        // 提醒非常
    }
}

瑕玷就是用户要写的代码有点多,可以看到上面的代码比较烦琐,一个要求就要搞这么一套东西。

Redux-promise

redus-promise 和 redux-thunk 的头脑相似,只不过做了一些简化,胜利失利手动 dispatch 被封装成自动了:

const FETCH_DATA = 'FETCH_DATA'
//action creator
const getData = function(id) {
    return {
        type: FETCH_DATA,
        payload: api.fetchData(id) // 直接将 promise 作为 payload
    }
}
//reducer
const reducer = function(oldState, action) {
    switch(action.type) {
    case FETCH_DATA: 
        if (action.status === 'success') {
             // 更新 store 等处置惩罚
        } else {
                // 提醒非常
        }
    }
}

适才的什么 then、catch 之类的被中间件自行处置惩罚了,代码简朴不少,不过要处置惩罚 Loading 啥的,还须要写分外的代码。

实在任何时候都是如许:封装少,自在度高,然则代码就会变庞杂;封装多,代码变简朴了,然则自在度就会变差。redux-thunk 和 redux-promise 恰好就是代表这两个面。

redux-thunk 和 redux-promise  的细致运用就不引见了,这里只聊一下也许的思绪。大部份简朴的异步营业场景,redux-thunk 或许 redux-promise 都可以满足了。

上面说的 Flux 和 Redux,和细致的前端框架没有什么关联,只是头脑和商定层面。下面就要和我们经常使用的 Vue 或 React 连系起来了:

Vuex

Vuex 重要用于 Vue,和 Flux,Redux 的头脑很相似。

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

Store

每一个 Vuex 内里有一个全局的 Store,包括着运用中的状况 State,这个 State 只是须要在组件中同享的数据,不必放一切的 State,没必要。这个 State 是单一的,和 Redux 相似,所以,一个运用仅会包括一个 Store 实例。单一状况树的优点是可以直接地定位任一特定的状况片断,在调试的历程当中也能轻易地取得悉数当前运用状况的快照。

Vuex经由历程 store 选项,把 state 注入到了悉数运用中,如许子组件能经由历程 this.$store 接见到 state 了。

const app = new Vue({
  el: '#app',
  // 把 store 对象供应给 “store” 选项,这可以把 store 的实例注入一切的子组件
  store,
  components: { Counter },
  template: `
    <div class="app">
      <counter></counter>
    </div>
  `
})
const Counter = {
  template: `<div>{{ count }}</div>`,
  computed: {
    count () {
      return this.$store.state.count
    }
  }
}

State 转变,View 就会随着转变,这个转变应用的是 Vue 的相应式机制。

Mutation

不言而喻,State 不能直接改,须要经由历程一个商定的体式格局,这个体式格局在 Vuex 内里叫做 mutation,变动 Vuex 的 store 中的状况的唯一要领是提交 mutation。Vuex 中的 mutation 非常相似于事宜:每一个 mutation 都有一个字符串的 事宜范例 (type) 和 一个 回调函数 (handler)。

const store = new Vuex.Store({
  state: {
    count: 1
  },
  mutations: {
    increment (state) {
      // 变动状况
      state.count++
    }
  }
})

触发 mutation 事宜的体式格局不是直接挪用,比方 increment(state)  是不可的,而要经由历程 store.commit 要领:

store.commit('increment')

注重:mutation 都是同步事件

mutation 有些相似 Redux 的 Reducer,然则 Vuex 不要求每次都搞一个新的 State,可以直接修正 State,这块儿又和 Flux 有些相似。具尤大的说法,Redux 强迫的 immutability,在保证了每一次状况变化都能追踪的情况下强迫的 immutability 带来的收益很有限,为了同构而设想的 API 很烦琐,必需依托第三方库才相对高效率地取得状况树的部分状况,这些都是 Redux 不足的处所,所以也被 Vuex 舍掉了。

到这里,实在可以感觉到 Flux、Redux、Vuex 三个的头脑都差不多,在细致细节上有一些差别,总的来讲都是让 View 经由历程某种体式格局触发 Store 的事宜或要领,Store 的事宜或要领对 State 举行修正或返回一个新的 State,State 转变今后,View 发作相应式转变。

Action

到这里又该处置惩罚异步这块儿了。mutation 是必需同步的,这个很好明白,和之前的  reducer 相似,差别步修正的话,会很难调试,不晓得转变什么时候发作,也很难肯定先后递次,A、B两个 mutation,挪用递次多是 A -> B,然则终究转变 State 的效果多是 B -> A。

对照Redux的中间件,Vuex 到场了 Action 这个东西来处置惩罚异步,Vuex的主意是把同步和异步拆离开,异步操纵想咋搞咋搞,然则不要滋扰了同步操纵。View 经由历程 store.dispatch(‘increment’) 来触发某个 Action,Action 内里不论实行若干异步操纵,完事今后都经由历程 store.commit(‘increment’) 来触发 mutation,一个 Action 内里可以触发多个 mutation。所以 Vuex 的Action 相似于一个天真好用的中间件。

Vuex 把同步和异步操纵经由历程 mutation 和 Action 来离开处置惩罚,是一种体式格局。但不代表是唯一的体式格局,另有许多体式格局,比方就不必 Action,而是在运用内部挪用异步要求,要求终了直接 commit mutation,固然也可以。

Vuex 还引入了 Getter,这个无足轻重,只不过是轻易盘算属性的复用。

Vuex 单一状况树并不影响模块化,把 State 拆了,末了组合在一同就行。Vuex 引入了 Module 的观点,每一个 Module 有自身的 state、mutation、action、getter,实在就是把一个大的 Store 拆开。

总的来看,Vuex 的体式格局比较清楚,适宜 Vue 的头脑,在现实开辟中也比较轻易。

对照Redux

Redux:
view——>actions——>reducer——>state变化——>view变化(同步异步一样)

Vuex:
view——>commit——>mutations——>state变化——>view变化(同步操纵)
view——>dispatch——>actions——>mutations——>state变化——>view变化(异步操纵)

React-redux

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猎取数据,其数据泉源于父组件。

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

假如一个组件既须要UI显现,又须要营业逻辑处置惩罚,那就得拆,拆成一个容器组件包着一个展示组件。

由于 react-redux 只是 redux 和 react 连系的一种完成,除了适才说的组件拆分,并没有什么新颖的东西,所以只拿一个简朴TODO项目的部份代码来举例:

进口文件 index.js,把 redux 的相干 store、reducer 经由历程 Provider 注册到 App 内里,如许子组件就可以拿到  store  了。

import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import rootReducer from './reducers'
import App from './components/App'

const store = createStore(rootReducer)

render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)

actions/index.js,建立 Action:

let nextTodoId = 0
export const addTodo = text => ({
  type: 'ADD_TODO',
  id: nextTodoId++,
  text
})

export const setVisibilityFilter = filter => ({
  type: 'SET_VISIBILITY_FILTER',
  filter
})

export const toggleTodo = id => ({
  type: 'TOGGLE_TODO',
  id
})

export const VisibilityFilters = {
  SHOW_ALL: 'SHOW_ALL',
  SHOW_COMPLETED: 'SHOW_COMPLETED',
  SHOW_ACTIVE: 'SHOW_ACTIVE'
}

reducers/todos.js,建立 Reducers:

const todos = (state = [], action) => {
  switch (action.type) {
    case 'ADD_TODO':
      return [
        ...state,
        {
          id: action.id,
          text: action.text,
          completed: false
        }
      ]
    case 'TOGGLE_TODO':
      return state.map(todo =>
        todo.id === action.id ? { ...todo, completed: !todo.completed } : todo
      )
    default:
      return state
  }
}

export default todos

reducers/index.js,把一切的 Reducers 绑定到一同:

import { combineReducers } from 'redux'
import todos from './todos'
import visibilityFilter from './visibilityFilter'

export default combineReducers({
  todos,
  visibilityFilter,
  ...
})

containers/VisibleTodoList.js,容器组件,connect 担任衔接React组件和Redux Store:

import { connect } from 'react-redux'
import { toggleTodo } from '../actions'
import TodoList from '../components/TodoList'

const getVisibleTodos = (todos, filter) => {
  switch (filter) {
    case 'SHOW_COMPLETED':
      return todos.filter(t => t.completed)
    case 'SHOW_ACTIVE':
      return todos.filter(t => !t.completed)
    case 'SHOW_ALL':
    default:
      return todos
  }
}

// mapStateToProps 函数指定怎样把当前 Redux store state 映照到展示组件的 props 中
const mapStateToProps = state => ({
  todos: getVisibleTodos(state.todos, state.visibilityFilter)
})

// mapDispatchToProps 要领吸收 dispatch() 要领并返回希冀注入到展示组件的 props 中的回调要领。
const mapDispatchToProps = dispatch => ({
  toggleTodo: id => dispatch(toggleTodo(id))
})

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(TodoList)

简朴来讲,react-redux 就是多了个 connect 要领衔接容器组件和UI组件,这里的“衔接”就是一种映照:

  • mapStateToProps  把容器组件的 state 映照到UI组件的 props
  • mapDispatchToProps 把UI组件的事宜映照到 dispatch 要领

Redux-saga

适才引见了两个Redux 处置惩罚异步的中间件 redux-thunk 和 redux-promise,固然 redux 的异步中间件另有许多,他们可以处置惩罚大部份场景,这些中间件的头脑基础上都是把异步要求部份放在了  action  creator  中,明白起来比较简朴。

redux-saga 采纳了别的一种思绪,它没有把异步操纵放在 action creator 中,也没有去处置惩罚 reductor,而是把一切的异步操纵算作“线程”,可以经由历程一般的action去触发它,当操纵完成时也会触发action作为输出。saga 的意义原本就是一连串的事宜。

redux-saga 把异步猎取数据这类的操纵都叫做副作用(Side  Effect),它的目的就是把这些副作用治理好,让他们实行更高效,测试更简朴,在处置惩罚毛病时更轻易。

在聊 redux-saga 之前,须要熟习一些准备学问,那就是 ES6 的 Generator

假如从没打仗过 Generator 的话,看着下面的代码,给你个1分钟傻瓜式速成,函数加个星号就是 Generator 函数了,Generator 就是个骂街天生器,Generator 函数里可以写一堆 yield 症结字,可以记成“丫的”,Generator 函数实行的时候,啥都不干,就等着挪用 next 要领,根据递次把标记为“丫的”的处所一个一个拎出来骂(遍历实行),骂到末了没有“丫的”标记了,就返回末了的return值,然后标记为 done: true,也就是骂完了(上面只是协助初学者影象,别喷~)。

function* helloWorldGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}

var hw = helloWorldGenerator();

hw.next() // 先把 'hello' 拎出来,done: false 代表还没骂完
// { value: 'hello', done: false } next() 要领有牢固的花样,value 是返回值,done 代表是不是遍历终了

hw.next() // 再把 'world' 拎出来,done: false 代表还没骂完
// { value: 'world', done: false }

hw.next() // 没有 yield 了,就把末了的 return 'ending' 拎出来,done: true 代表骂完了
// { value: 'ending', done: true }

hw.next() // 没有 yield,也没有 return 了,真的骂完了,只能挤出来一个 undefined 了,done: true 代表骂完了
// { value: undefined, done: true }

如许搞有啥优点呢?我们发明 Generator 函数的许多代码可以被延缓实行,也就是具有了停息和影象的功用:碰到yield表达式,就停息实行背面的操纵,并将紧跟在yield背面的谁人表达式的值,作为返回的对象的value属性值,等着下一次挪用next要领时,再继承往下实行。用 Generator 来写异步代码,也许长如许:

function* gen(){
  var url = 'https://api.github.com/users/github';
  var jsonData = yield fetch(url);
  console.log(jsonData);
}

var g = gen();
var result = g.next(); 
// 这里的result是 { value: fetch('https://api.github.com/users/github'), done: true }

// fetch(url) 是一个 Promise,所以须要 then 来实行下一步
result.value.then(function(data){
  return data.json();
}).then(function(data){
  // 猎取到 json data,然后作为参数挪用 next,相当于把 data 传给了 jsonData,然后实行 console.log(jsonData);
  g.next(data);
});

再回到 redux-saga 来,可以把 saga 设想成开了一个以最快速率不断地挪用 next 要领并尝试猎取一切 yield 表达式值的线程。举个例子:

// saga.js
import { take, put } from 'redux-saga/effects'

function* mySaga(){ 
    // 壅塞: take要领就是守候 USER_INTERACTED_WITH_UI_ACTION 这个 action 实行
    yield take(USER_INTERACTED_WITH_UI_ACTION);
    // 壅塞: put要领将同步提议一个 action
    yield put(SHOW_LOADING_ACTION, {isLoading: true});
    // 壅塞: 将守候 FetchFn 终了,守候返回的 Promise
    const data = yield call(FetchFn, 'https://my.server.com/getdata');
    // 壅塞: 将同步提议 action (运用适才返回的 Promise.then)
    yield put(SHOW_DATA_ACTION, {data: data});
}

这里用了好几个yield,简朴明白,也就是每一个 yield 都提议了壅塞,saga 会守候实行效果返回,再实行下一指令。也就是相当于take、put、call、put 这几个要领的挪用变成了同步的,上面的悉数完成返回了,才会实行下面的,相似于 await。

用了 saga,我们就可以很细粒度的掌握各个副作用每一部的操纵,可以把异步操纵和同步提议 action 一同,随意的排列组合。saga 还供应 takeEvery、takeLatest 之类的辅佐函数,来掌握是不是许可多个异步要求同时实行,尤其是 takeLatest,轻易处置惩罚由于网络耽误形成的屡次要求数据争执或杂沓的题目。

saga 看起来很庞杂,重要原因多是由于人人不熟习 Generator 的语法,另有须要进修一堆新增的 API 。假如抛开这些影象的东西,革新一下,再来看一下代码:

function mySaga(){ 
    if (action.type === 'USER_INTERACTED_WITH_UI_ACTION') {
        store.dispatch({ type: 'SHOW_LOADING_ACTION', isLoading: true});
        const data = await Fetch('https://my.server.com/getdata');
        store.dispatch({ type: 'SHOW_DATA_ACTION', data: data});
    }
}

上面的代码就很清楚了吧,悉数都是同步的写法,非常顺畅,固然直接如许写是不支撑的,所以那些 Generator 语法和API,不过就是做一些适配罢了。

saga 还能很轻易的并行实行异步使命,或许让两个异步使命合作:

// 并行实行,并守候一切的效果,相似 Promise.all 的行动
const [users, repos] = yield [
  call(fetch, '/users'),
  call(fetch, '/repos')
]

// 并行实行,哪一个先完成返回哪一个,剩下的就作废掉了
const {posts, timeout} = yield race({
  posts: call(fetchApi, '/posts'),
  timeout: call(delay, 1000)
})

saga 的每一步都可以做一些断言(assert)之类的,所以非常轻易测试。而且很轻易测试到差别的分支。

这里不议论更多 saga 的细节,人人相识 saga 的头脑就行,细节请看文档

对照 Redux-thunk

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

比较一下 redux-thunk 和 redux-saga 的代码:

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

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

和 redux-thunk 等其他异步中间件对照来讲,redux-saga 重要有下面几个特性:

  • 异步数据猎取的相干营业逻辑放在了零丁的 saga.js 中,不再是搀杂在 action.js 或 component.js 中。
  • dispatch 的参数是规范的  action,没有魔法。
  • saga 代码采纳相似同步的体式格局誊写,代码变得更易读。
  • 代码非常/要求失利 都可以直接经由历程 try/catch 语法直接捕捉处置惩罚。
  • 很轻易测试,假如是 thunk 的 Promise,测试的话就须要不断的 mock 差别的数据。

实在 redux-saga 是用一些进修的庞杂度,换来了代码的高可保护性,照样很值得在项目中运用的。

Dva

Dva是什么呢?官方的定义是:dva 首先是一个基于 redux 和 redux-saga 的数据流计划,然后为了简化开辟体验,dva 还分外内置了 react-router 和 fetch,所以也可以明白为一个轻量级的运用框架。

简朴明白,就是让运用 react-redux 和 redux-saga 编写的代码组织起来更合理,保护起来更轻易。

之前我们聊了 redux、react-redux、redux-saga 之类的观点,人人一定以为头昏脑涨的,什么 action、reducer、saga 之类的,写一个功用要在这些js文件内里不断的切换。

dva 做的事变很简朴,就是让这些东西可以写到一同,不必离开来写了。比方:

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 };
    },
  },
});

之前誊写的体式格局是建立  sagas/products.js, reducers/products.js 和 actions/products.js,然后把 saga、action、reducer 啥的离开来写,往返切换,如今写在一同就轻易多了。

比方传统的 TODO 运用,用 redux + redux-saga 来示意构造,就是如许:

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

saga 阻拦 add 这个 action, 提议 http 要求, 假如要求胜利, 则继承向 reducer 发一个 addTodoSuccess 的 action, 提醒建立胜利, 反之则发送 addTodoFail 的 action 即可。

假如运用 Dva,那末构造图以下:

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

悉数构造变化不大,最重要的就是把 store 及 saga 一致为一个 model 的观点(有点相似 Vuex 的 Module),写在了一个 js 文件里。增添了一个 Subscriptions, 用于网络其他泉源的 action,比方快捷键操纵。

app.model({
  namespace: 'count',
  state: {
    record: 0,
    current: 0,
  },
  reducers: {
    add(state) {
      const newCurrent = state.current + 1;
      return { ...state,
        record: newCurrent > state.record ? newCurrent : state.record,
        current: newCurrent,
      };
    },
    minus(state) {
      return { ...state, current: state.current - 1};
    },
  },
  effects: {
    *add(action, { call, put }) {
      yield call(delay, 1000);
      yield put({ type: 'minus' });
    },
  },
  subscriptions: {
    keyboardWatcher({ dispatch }) {
      key('⌘+up, ctrl+up', () => { dispatch({type:'add'}) });
    },
  },
});

之前我们说过商定优于设置的头脑,Dva正式自创了这个头脑。

MobX

前面扯了这么多,实在还都是 Flux 系统的,都是单向数据流计划。接下来要说的 MobX,就和他们不太一样了。

我们先清空一下大脑,回到初心,什么是初心?就是我们最初要处置惩罚的题目是什么?最初我们实在为了处置惩罚运用状况治理的题目,不论是 Redux 照样 MobX,把状况治理好是条件。什么叫把状况治理好,简朴来讲就是:一致保护大众的运用状况,以一致而且可控的体式格局更新状况,状况更新后,View随着更新。不论是什么头脑,杀青这个目的就ok。

Flux 系统的状况治理体式格局,只是一个选项,但并不代表是唯一的选项。MobX 就是另一个选项。

MobX背地的哲学很简朴:任何源自运用状况的东西都应当自动地取得。译成人话就是状况只需一变,其他用到状况的处所就都随着自动变。

《Vuex、Flux、Redux、Redux-saga、Dva、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 许可有多个 store,而且这些 store 里的 state 可以直接修正,不必像 Redux 那样每次还返回个新的。这个有点像 Vuex,自在度更高,写的代码更少。不过它也会让代码不好保护。

MobX 和 Flux、Redux 一样,都是和细致的前端框架无关的,也就是说可以用于 React(mobx-react) 或许 Vue(mobx-vue)。平常来讲,用到 React 比较罕见,很少用于 Vue,由于 Vuex 自身就相似 MobX,很天真。假如我们把 MobX 用于 React  或许  Vue,可以看到许多 setState() 和 this.state.xxx = 如许的处置惩罚都可以省了。

照样和上面一样,只引见头脑。细致 MobX 的运用,可以看这里

对照 Redux

我们直观地上两坨完成计数器代码:

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

Redux:

import React, { Component } from 'react';
import {
  createStore,
  bindActionCreators,
} from 'redux';
import { Provider, connect } from 'react-redux';

// ①action types
const COUNTER_ADD = 'counter_add';
const COUNTER_DEC = 'counter_dec';

const initialState = {a: 0};
// ②reducers
function reducers(state = initialState, action) {
  switch (action.type) {
  case COUNTER_ADD:
    return {...state, a: state.a+1};
  case COUNTER_DEC:
    return {...state, a: state.a-1};
  default:
    return state
  }
}

// ③action creator
const incA = () => ({ type: COUNTER_ADD });
const decA = () => ({ type: COUNTER_DEC });
const Actions = {incA, decA};

class Demo extends Component {
  render() {
    const { store, actions } = this.props;
    return (
      <div>
        <p>a = {store.a}</p>
        <p>
          <button className="ui-btn" onClick={actions.incA}>增添 a</button>
          <button className="ui-btn" onClick={actions.decA}>削减 a</button>
        </p>
      </div>
    );
  }
}

// ④将state、actions 映照到组件 props
const mapStateToProps = state => ({store: state});
const mapDispatchToProps = dispatch => ({
  // ⑤bindActionCreators 简化 dispatch
  actions: bindActionCreators(Actions, dispatch)
})
// ⑥connect发生容器组件
const Root = connect(
  mapStateToProps,
  mapDispatchToProps
)(Demo)

const store = createStore(reducers)
export default class App extends Component {
  render() {
    return (
      <Provider store={store}>
        <Root />
      </Provider>
    )
  }
}

MobX:

import React, { Component } from 'react';
import { observable, action } from 'mobx';
import { Provider, observer, inject } from 'mobx-react';

// 定义数据构造
class Store {
  // ① 运用 observable decorator 
  @observable a = 0;
}

// 定义对数据的操纵
class Actions {
  constructor({store}) {
    this.store = store;
  }
  // ② 运用 action decorator 
  @action
  incA = () => {
    this.store.a++;
  }
  @action
  decA = () => {
    this.store.a--;
  }
}

// ③实例化单一数据源
const store = new Store();
// ④实例化 actions,而且和 store 举行关联
const actions = new Actions({store});

// inject 向营业组件注入 store,actions,和 Provider 合营运用
// ⑤ 运用 inject decorator 和 observer decorator
@inject('store', 'actions')
@observer
class Demo extends Component {
  render() {
    const { store, actions } = this.props;
    return (
      <div>
        <p>a = {store.a}</p>
        <p>
          <button className="ui-btn" onClick={actions.incA}>增添 a</button>
          <button className="ui-btn" onClick={actions.decA}>削减 a</button>
        </p>
      </div>
    );
  }
}

class App extends Component {
  render() {
    // ⑥运用Provider 在被 inject 的子组件里,可以经由历程 props.store props.actions 接见
    return (
      <Provider store={store} actions={actions}>
        <Demo />
      </Provider>
    )
  }
}

export default App;

比较一下:

  • Redux 数据流活动很天然,可以充分应用时候回溯的特性,加强营业的可展望性;MobX 没有那末天然的数据活动,也没有时候回溯的才能,然则 View 更新很准确,粒度掌握很细。
  • Redux 经由历程引入一些中间件来处置惩罚副作用;MobX  没有中间件,副作用的处置惩罚比较自在,比方依托 autorunAsync 之类的要领。
  • Redux 的榜样代码更多,看起来就像是我们要做顿饭,须要先买个调料盒装调料,再买个架子放刀叉。。。做一大堆准备工作,然后才最先炒菜;而 MobX 基础没啥过剩代码,直接硬来,拿着炊具调料就开干,搞出来为止。

但实在 Redux 和 MobX 并没有孰优孰劣,Redux 比 Mobx 更多的榜样代码,是由于特定的设想束缚。假如项目比较小的话,运用 MobX 会比较天真,然则大型项目,像 MobX 如许没有束缚,没有最好实践的体式格局,会形成代码很难保护,各有利弊。平常来讲,小项目发起 MobX 就够了,大项目照样用 Redux 比较适宜。

总结

时候荏苒,光阴似箭。每一个框架或许库只能陪你走一段路,终究都邑逝去。留在你心中的,不是一条一条的语法划定规矩,而是一个一个的头脑,这些头脑才是推进提高的源泉。

帅哥玉人,假如你都看到这里了,那末不点个赞,你的良知过得去么?

参考链接

https://cn.vuejs.org/v2/guide/state-management.html
https://vuex.vuejs.org/
https://cn.redux.js.org/docs/react-redux/
http://www.ruanyifeng.com/blog/2016/09/redux_tutorial_part_two_async_operations.html
http://www.ruanyifeng.com/blog/2016/09/redux_tutorial_part_three_react-redux.html
https://redux-saga-in-chinese.js.org
https://juejin.im/post/59e6cd68f265da43163c2821
https://react-redux.js.org/introduction/why-use-react-redux
https://segmentfault.com/a/1190000007248878
http://es6.ruanyifeng.com/#docs/generator
https://juejin.im/post/5ac1cb9d6fb9a028cf32a046
https://zhuanlan.zhihu.com/p/35437092
https://github.com/dvajs/dva/issues/1
https://cn.mobx.js.org
https://zhuanlan.zhihu.com/p/25585910
http://imweb.io/topic/59f4833db72024f03c7f49b4

    原文作者:姜小抖
    原文地址: https://segmentfault.com/a/1190000017420120
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞