JS核心理论之《Redux、Vuex、Mobx状态管理》

463 阅读5分钟

近年来,SPA单页应用越来越多,前端工作的复杂度也在直线上升。前端页面上展示的信息越来越多也越来越复杂,页面组件变得臃肿,组件间的通信成本增高,数据流向变得模糊。 因此需要一个系统的状态管理方案。状态管理的解决思路就是:把组件之间需要共享的状态抽取出来,遵循特定的约定,统一来管理,让状态的变化可以预测

Flux、Redux、Vuex、MobX 是常用的状态管理模式。

Flux

image

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

Flux把一个应用分成了4个部分:View、Action、Dispatcher、Store。核心是 Dispatcher,通过 Dispatcher,用户可以注册需要相应的 action 类型,对不同的 action 注册对应的回调,以及触发 action 并传递 payload 数据。

 action             传递action         update state
------> dispatcher ---------> stores ------------> views

示例:

const dispatcher = new Dispatcher()
const store = {books: []}

dispatcher.register((payload) => {
    if (payload.actionType === 'addBook') {
        store.books.push(payload.newBook)
    }
})

dispatcher.dispatch({
    actionType: 'addBook',
    newBook: {
        name: 'cookbook'
    }
})

Redux

Reflux 是在 Flux 的基础上编写的一个 Flux 实现,从形式上看,去掉了显式的 Dispatcher,将 action 表现为函数的形式

image

  • 所有的状态存放在 Store。组件每次重新渲染,都必须由状态变化引起。用户在 UI 上发出 action。reducer 函数接收 action,然后根据当前的 state,计算出新的 state。
  • Redux store 是单一数据源。Redux 没有 dispatcher 的概念,转而使用纯函数Reducer代替。
  • Redux store 是不可变的(Immutable)。

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

                  call             new state
action --> store ------> reducers -----------> view

示例:

const { List } = require('immutable')
import { createStore } from 'redux'

const initialState = {
    books: List([])
}

// action
const addBook = (book) => {
    return {
        type: 'ADD_BOOK',
        book
    }
}

// reducer
const books = (state = initialState, action) => {
    switch (action.type) {
        case 'ADD_BOOK':
        return Object.assign({}, state, {
            books: state.books.push(action.book)
        })
    }
    return state
}

// store
const bookStore = createStore(books, initialState)

// dispatching action
store.dispatch(addBook({/* new book */}))

Vuex

Vuex 主要用于 Vue,和 Flux,Redux 的思想很相似。

image

  • 单向数据流。View 通过 store.dispatch() 调用 Action ,在 Action 执行完异步操作之后通过 store.commit() 调用 Mutation 同步更新 State ,通过 vue 的响应式机制进行视图更新。
  • 单一数据源。和 Redux 一样全局只有一个 Store 实例。
  • 可直接对 State 进行修改。Mutaion 是 vuex 中改变 State 的唯一途径(严格模式下),并且只能是同步操作。Vuex 中通过 store.commit() 调用 Mutation 。

当 Store 对象过于庞大时,可根据具体的业务需求分为多个 Module ,每个 Module 都具有自己的 state 、mutation 、action 、getter。

       commit           mutate        render
action ------> mutation ------> state ------> view

示例:

// store
const store = {
    books: []
}


// mutations
const mutations = {
    [ADD_BOOKS](state, book) {
        state.books.push(book)
    }
}

// actions
const actions = {
    addBook({ commit }) {
        request.get(BOOK_API).then(res => {
          commit(ADD_BOOK, res.body.new_book)
        })
    }
}

// dispatching action
store.dispatch('addBook')

Mobx

image

  • State: 驱动应用的数据。
  • Computed values: 计算值。如果你想创建一个基于当前状态的值时,请使用 computed。
  • Reactions: 反应,当状态改变时自动发生。
  • Actions: 动作,用于改变 State 。
  • 依赖收集(autoRun): MobX 中的数据以来基于观察者模式,通过 autoRun 方法添加观察者。

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

       modify         trigger
action ------> state  -------> views

示例:

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

class BookModel {  
    @observable
    books = []

    @action
    addBook = (new_book) => {
        this.books.push(new_book);
    }

    @computed get total() {
        return this.books.concat(['default']);
    }
}

const bookModel = new BookModel();

@inject('bookModel') 
@observer
class App extends React.Component {  
    render() {
        const { books, addBook } = this.props.bookModel;

        return (
            <div>
                <span>{books.length}</span>
                <button onClick={addBook}>addBook</button>
            </div>
        )
    }
}

ReactDOM.render(  
    <Provider bookModel={bookModel}>
        <App />
    </Provider>
);

Mobx不要求数据在一颗树上,因此对Mobx进行数据可是化或者是记录每次的数据变化变得不太容易,尤其在大型项目中。 在Mobx的基础上,Mobx State Tree诞生了。同Redux一样,Mobx State Tree要求数据在一颗树上,这样对数据进行可视化和追踪就变得非常容易。

import React from 'react';  
import ReactDOM from 'react-dom';  
import { types } from 'mobx-state-tree';  
import { Provider, observer, inject } from 'mobx-react';

const BookModel = types.model('BookModel', {  
    book: types.array
}).actions(self => ({
    addBook(new_book) {
      self.book.push(new_book);
    }
}));

const store = BookModel.create({  
    book: []
});

@inject(({ store }) => ({ book: store.book, addBook: store.addBook }))
class App extends React.Component {  
    render() {
        const { book, addBook } = this.props;
        return (
            <div>
                <span>{books.length}</span>
                <button onClick={addBook}>addBook</button>
            </div>
        )
    }
}

ReactDOM.render(  
    <Provider store={store}>
        <App />
    </Provider>
);

Mobx使用建议

  1. 正确使用reactions:autorunautorunAsyncwhenreaction。如果你有一个函数应该自动运行,但不会产生一个新的值,请使用autorun。 其余情况都应该使用 computed。
  2. 使用严格模式。默认情况下,MobX允许你随便的更新observable数据。在大型应用中,若随意的变更数据会使程序状态无法追踪,不可预测。为此,MobX 提供了严格模式,强迫我们只可以在 action 中更新observable数据。
  3. 使用多个 store。一个用于 UI 状态;一个或多个用于领域状态。

注意点

  • Flux 、Redux 、Vuex 均为单向数据流。
  • Redux 和 Vuex 是基于 Flux 的,Redux 较为泛用,Vuex 只能用于 vue。
  • Flux 与 MobX 可以有多个 Store ,Redux 、Vuex 全局仅有一个 Store(单状态树)。
  • Redux 、Vuex 适用于大型项目的状态管理,MobX 在大型项目中应用会使代码可维护性变差,一般需要结合Mobx State Tree
  • Redux 中引入了中间件,主要解决异步带来的副作用,可通过约定完成许多复杂工作。
  • MobX 是状态管理库中代码侵入性最小的之一,具有细粒度控制、简单可扩展等优势,但是没有时间回溯能力,一般适合应用于中小型项目中。