前言

对于复杂点的前端应用来说,状态管理是一个避不开的问题。应用的组件间需要共享某些状态,此时如果不对状态进行有效地管理,则会非常混乱,状态的改变会无法跟踪。此时工程师给出的思路就是将应用的状态提取出来,统一管理,严格按照流程来进行修改,让状态变化可预测。这里的祖师爷即为Flux,而ReduxVuex为Flux架构的继承者,根据自身框架的特点进行改进,下面分别聊聊这三种状态管理架构

Flux

image.png
Flux把应用分为四部分:

  1. View:视图层,根据Store的数据渲染页面
  2. Store:负责存储数据,发生变动时通知View进行更新
  3. Action:关于修改Store数据的行为描述,一般是一个对象
  4. Dispatcher:接收Action作为参数并将其派发给所有Store

总结下Flux架构下用户交互的一般流程就是:

  1. View层中用户通过点击或者其他操作触发回调
  2. 回调中调用Dispatcher来派发Action所有Store
  3. 接收到ActionStore根据Action的内容进行更新数据,并派发事件等方式通知对应的View层也进行更新

Flux架构下有以下特点:

  1. 一个应用下可以有多个Store,多个Store可以分别跟多个View进行绑定
  2. Store的变化只能通过Dispatcher分发Action来实现,这保证了数据的单向流动,保证流程的清晰
  3. 修改State的可以不是纯函数

具体架构如图所示:
image.png

Redux

与Flux的区别

  • 在Flux中,一个应用可能有多个Store,这些Store会分别于多个View进行绑定,这就会让状态的变化可能会变得不够清晰。所以Redux对此进行了改进,一个应用只能拥有一个Store。
  • 由于只有一个Store,所以就没有了dispatcher的概念,dispatch方法被集成到了Store当中
  • 只能使用纯函数得到新的State,不能修改State。

三大原则

  1. 单一数据源,整个应用的state存储在一个状态树上面,而这个状态树存储于唯一的一个store中。这使得保存应用状态变得非常简单。
  2. state只读,除了dispatch action,任何其他方法都无法直接修改state,通过这种集中化处理,可调试性大大提高
  3. 只能使用纯函数修改,每次都返回一个新对象,这使得时间旅行之类的功能成为可能。

    核心概念

    Reducer

    一个以接收旧state和action作为参数,返回新state的纯函数。这个函数不能在原有state对象上进行修改,而是要返回一个全新的state,这样View才能更新。

    Action

    与Flux的Action概念基本一致,是一个描述改变Store数据的对象。
    其中Redux还提出ActionCreator的概念,是一个生成Action的函数,避免用户重复生成Action的麻烦。

    Store

    通过 createStore 函数接收一个 reducer 作为参数创建。

    核心API

    createStore(reducer, [initialState], [enhancer])

    Redux通过此方法创建Store

  4. reducer,接收state与action作为参数,返回下一个状态树

  5. initialState,Store的初始状态树
  6. enhancer,用于加强Store的功能,一般为applyMiddleware

    combineReducers(reducers)

    用于结合多个Reducer,Redux通过此方法来实现状态的模块化管理,以避免Flux当中存在多个Store的情况。
    其中reducers的组织形式是一个key-reducer的对象,创建对应的store对应的state树也可以通过对应的key来得到对应的state,例子如下:
    1. const todosReducer = (state = [], action) => {}
    2. const counterReducer = (state = 0, action) => {}
    3. const reducers = combineReducers({
    4. todos: todosReducer,
    5. counter: counterReducer
    6. })
    7. const store = createStore(reducers)
    8. console.log(store.getState())
    9. // {
    10. // todos: ...,
    11. // counter: ...
    12. // }

    applyMiddleware(…middleware)

    中间件是Redux提供的位于Action发起之后,Reducer执行前的扩展点。在这个扩展点中常见的应用是打印日志,异步IO等等。
    一个经典的middleware的函数结构如下:
    1. const logger = store => next => action => {
    2. console.group(action.type)
    3. console.info('dispatching', action)
    4. let result = next(action)
    5. console.log('next state', store.getState())
    6. console.groupEnd(action.type)
    7. return result
    8. }
    具体的演变思路可以看这里,每个中间件的目的就是要对前一个 dispatch 函数进行封装形成一个新的 dispatch 函数。所以applyMiddleware的大体实现如下: ```javascript function applyMiddleware(…middlewares) { return (createStore) => (reducer, preloadedState) => { const store = createStore(reducer, preloadedState)
    1. const middlewareAPI = {
    getState: store.getState, dispatch: (action, …args) => dispatch(action, …args) } const chain = middlewares.map(middleware => middleware(middlewareAPI)) dispatch = compose(…middlewares)(store.dispatch) return { …store, dispatch } } }
  1. 其中compose函数是函数式编程的一个概念,它的意思是将多个同类型函数(功能一致,参数类型、返回值类型一致)进行组合,形成一个新的函数。具体实现如下:
  2. ```javascript
  3. function compose(...funcs) {
  4. if (funcs.length === 0) {
  5. return (arg) => arg
  6. }
  7. if (funcs.length === 1) {
  8. return funcs[0]
  9. }
  10. return funcs.reduce((a, b) => (...args: any) => a(b(...args)))
  11. }

总结流程就是先将中间件数组映射为传入了store后的形状为 next => action => {...} 函数数组。接着通过compose将这个函数数组进行组合,此时得到的还是形状为 next => action => {...} 的函数。接着将最初始的 store.dispatch 传入此函数,最终得到需要的 dispatch 函数。

bindActionCreators(actionCreator, dispatch)

ActionCreator即为创建Action的函数,bindActionCreators就是将action跟store.dispatch进行绑定,直接向绑定后的函数传入payload即可自动dispatch相关的action。例子如下:

  1. const CounterAddActionCreator = (count) => {
  2. return {
  3. type: 'ADD_COUNT',
  4. count
  5. }
  6. }
  7. const boundCounterAddAction = bindActionCreator(CounterAddActionCreator, store.dispatch)
  8. boundCounterAddAction(10) // 相当于 store.dispatch(CounterAddActionCreator(10))

store.dispatch(action)

分发Action

store.subscribe(listener)

添加监听函数

store.getState()

获取store的state状态树

Vuex

Vue当中的状态管理工具为Vuex,它的核心理念也是单向数据流

核心概念

State

与Redux类似,Vuex中的State也是单一状态树,一个对象包含应用的所有状态。

Getter

类似于计算属性,每个getter接收state作为参数,返回运算后的值,相关的值会进行缓存,直至依赖值发生变化。

Mutation

提交mutation是Vuex中改变state的唯一方式,在这里处理的只能是同步内容

通过在配置对象的mutations选项下添加对应方法即可完成mutation的注册,每个mutation方法接收state与payload作为参数,然后对state的内容进行修改。通过 store.commit 方法提交mutation,完成对state的修改。具体例子如下:

  1. const store = new Vuex.Store({
  2. state: {
  3. count: 1
  4. },
  5. mutations: {
  6. increment (state) {
  7. state.count++
  8. }
  9. }
  10. })
  11. store.commit('increment')

Action

Action的功能是提交mutation和进行任意具有副作用的操作(包括异步请求、修改localStorage等等)。这点是Vuex比Redux更为强大的地方,Redux原则上不支持任何的副作用操作,要进行这些操作必须要先通过中间件。

Module

Vuex中的模块化方案为Module,每个module都有自己的state, getter, mutation, action甚至module。

对比Vuex与Redux

相同点

  1. 单个应用只能有一个状态树
  2. 两者理念都是单向数据流,只能通过一种方式改变状态,Vuex为mutation,Redux为Reducer

    不同点

  3. Vuex原生提供给用户处理副作用的机制(Action选项),而Redux原生并不支持副作用,必须要通过中间件进行处理(redux-thunk)

  4. Redux要求每次都要生成全新的state,而Vuex则不需要,只需要保证在mutation上修改state即可
  5. Redux的模块化方案为分割成多个Reducer,然后通过combineReducers结合。Vuex则通过Module选项实现模块化