Backbone Design Patterns - Frontend

2021, Jun 12    

Comparison for frontend backbone design patterns: Flux, Redux, Vuex. Since Redux is commonly know as a replacement for Flux(declared event in its official website) and the development of Vuex has taken a lot of reference from Redux, so in the overall usecases, they are very similar.

In this article, I went through all the integration documents of the three and made a short summary on each of the frameworks core concepts.

Flux

老牌的 State 状态管理方案,核心模型有:

  • Store: 状态储存对象.
  • Dispatcher: action 全局路由对象,store 需要把自己注册到Dispatcher 方便监听全局事件, store 以及store 之间可以通过 wait 等等方式设定依赖关系.
var flightDispatcher = new Dispatcher();

// Keeps track of which country is selected
var CountryStore = {country: null};

// Keeps track of which city is selected
var CityStore = {city: null};

// Keeps track of the base flight price of the selected city
var FlightPriceStore = {price: null};


CountryStore.dispatchToken = flightDispatcher.register(function(payload) {
  if (payload.actionType === 'country-update') {
    CountryStore.country = payload.selectedCountry;
  }
});

CityStore.dispatchToken = flightDispatcher.register(function(payload) {
  if (payload.actionType === 'country-update') {
    // `CountryStore.country` may not be updated.
    flightDispatcher.waitFor([CountryStore.dispatchToken]);
    // `CountryStore.country` is now guaranteed to be updated.

    // Select the default city for the new country
    CityStore.city = getDefaultCityForCountry(CountryStore.country);
  }
});

flightDispatcher.dispatch({
  actionType: 'city-update',
  selectedCity: 'paris'
});
  • Container 属于用来绑定Store -> Component 内部状态的封装器:
import { Component } from 'react';
import { Container } from 'flux/utils';

class CounterContainer extends Component {
  static getStores() {
    return [CounterStore];
  }

  static calculateState(prevState) {
    return {
      counter: CounterStore.getState(),
    };
  }

  render() {
    return <CounterUI counter={this.state.counter} />;
  }
}

const container = Container.create(CounterContainer);

因为大部分Flux 中的使用场景随着 Hook 中的useContext 以及useReducer 能够被直接替代,因此目前Flux已经推出历史舞台了.

Redux

核心元素:

  • Store
    • dispatch action
    • subscribe to store’s state update
  • Reducer
    • 处理action, 支持一个store 下注册多个reducer.
    • 可以通过createSlice 生成对应的 reducer 切面,然后挂载到store 下面,对特定状态,特定操作进行处理.

使用方法:

  • Component 通过Hook 进行获取:
    • useDispatcher.
    • useSelector 进行state 状态获取.
  • Connect()
    • 通过一些关键参数比如说:mapStateProps, mapDispatchProps component 进行封装.

具体API 接口, 参考 redux-toolkit

Vuex

下面一幅图看循环的几个step,Components 指代Vue视觉元素, action 表示抽象的各种可能触发数据更新的场景 对mutation 做出了明确的封装, Mutations 指代Store 中注册的mutation 字段中的相关可以对property 进行修改的函数.

  • State
    • 每个app 只有统一的一个单状态树.
    • 对于多个store state 到property 转换,可以使用mapState 函数.
    • components 可以有local state(property)与 global state 并存.
	const Counter = {
		template: `<div>8</div>`,
		computed: {
			count () {
				return this.$store.state.count
			}
		}
	}

	computed: {
		localComputed () { /* ... */ },
		// mix this into the outer object with the object spread operator
		...mapState({
			// ...
		})
	}

store 中的mutation 集中注册了可以修改状态的方法,需要通过commit 调用.

const store = createStore({
  state: {
    count: 1
  },
  mutations: {
    increment (state) {
      // mutate state
      state.count++
    }
  }
})

store.commit('increment')
// 也可以传入 mutation 的参数,
// store.commit('increment', 10)
// increment by 10

  • Actions

对mutation 进行的封装, 内部支持sync/async 操作.

const store = createStore({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  },
  actions: {
    increment (context) {
      context.commit('increment')
    }
  }
})

store.dispatch('increment')

global store 可以拆分为小的module,module 可以含有 state,mutation,actions,getters 甚至是nested modules 对象. modules中的各类方法,比如说 getters , actions 等等 都跟随module 的名称自动添加namespace:


modules: {
  foo: {
    namespaced: true,

    getters: {
      // `getters` is localized to this module's getters
      // you can use rootGetters via 4th argument of getters
      someGetter (state, getters, rootState, rootGetters) {
        getters.someOtherGetter // -> 'foo/someOtherGetter'
        rootGetters.someOtherGetter // -> 'someOtherGetter'
        rootGetters['bar/someOtherGetter'] // -> 'bar/someOtherGetter'
      },
      someOtherGetter: state => { ... }
    },

    actions: {
      // dispatch and commit are also localized for this module
      // they will accept `root` option for the root dispatch/commit
      someAction ({ dispatch, commit, getters, rootGetters }) {
        getters.someGetter // -> 'foo/someGetter'
        rootGetters.someGetter // -> 'someGetter'
        rootGetters['bar/someGetter'] // -> 'bar/someGetter'

        dispatch('someOtherAction') // -> 'foo/someOtherAction'
        dispatch('someOtherAction', null, { root: true }) // -> 'someOtherAction'

        commit('someMutation') // -> 'foo/someMutation'
        commit('someMutation', null, { root: true }) // -> 'someMutation'
      },
      someOtherAction (ctx, payload) { ... }
    }
  }
}

TOC