背景

在我们传统的开发模式中,没有用到复杂的状态管理,只是应用了组件内部State以及对外Props属性,相对于简单的应用开发是可以满足的,当遇到复杂的应用后,组件内部的状态过多,维护的也就越复杂,组件之间的“信息”传递也就会困难,稍不注意就会出现问题,这也是开发者头疼的事情。Redux、Mobx等状态管理解决方案的出现在技术上解决了开发者的难题,但上手门槛、概念多、样板式代码等问题也随着暴露,“当年的屠龙少年,自己变成了一条龙”。
**

mirrorx — 更简单清晰的解决方案

基于以上实践考虑,我们在ucf-web中选择引入 mirrorx 模型框架来解决这个困扰的问题,它不是横空出世的新物种,它只是在 Redux 之上的衍生解决方案,继承了Redux的单一数据源、数据不可变、纯函数执行等三大原则的优势,并解决了概念多、样板式代码、状态树维护难等问题。

使用 mirrorx ,主要工作在于如何定义 model 模型文件。

定义model

一个基本的model如下这个样子:

  1. /**
  2. * 数据模型类
  3. */
  4. export default {
  5. name: "app",
  6. initialState: {
  7. },
  8. reducers: {
  9. updateState(state, data) {
  10. return {
  11. ...state,
  12. ...data
  13. };
  14. }
  15. },
  16. effects: {
  17. }
  18. };

可以看出仅仅只有4个字段,下面来详细解读一下4个字段具体使用含义。

name

顾名思义,模型的名称,每一个业务对应一个模型通过该字段的定义来找到对应的方法。

要创建 model,必须要指定 name,且为一个合法字符串。name 很好理解,就是 model 的名称,这个名称会用于后面创建的 Redux store 里的命名空间。

假设定义了一个这样的 model:

  1. export default {
  2. name: 'app'
  3. };

那么最后创建的 Redux store 会是这样的结构:

  1. store.getState();
  2. // {app: null}

可以看到,model 的 name 就是其 state 在根 store 下的命名空间(当然,name 对全局 actions 也非常重要,见下文)。
另外,需要注意的是,上面创建的 store,其 app 这个 state 的值是 null,假如你想要一个不同的、更有意义的值,那么你就需要指定一个 initialState

注意:Mirror 使用了 react-router-redux,因此你不可以使用 routing 作为 model 的 name

initialState

initialState 也很容易理解,表示 model 的初始 state。在创建标准的 Redux reducer 时,它就表示这个 reducer 的 initialState

常规组件内部的state应该在这里修改写在initialState里面,这个值不是必需的,而且可以为任意值。如果没有指定 initialState,那么它的值就是 null

创建 model:

  1. export default {
  2. name: 'app',
  3. + initialState: {
  4. + num : 0
  5. + }
  6. };

得到的 store:

  1. store.getState();
  2. // { app: { num : 0 } }

reducers

Mirror app 所有的 Redux reducer 都是在 reducers 中定义的,reducers 对象中的方法本身会用于创建 reducer,方法的名字会用于创建 action type。Mirror 的原则是,一个 reducer 只负责一个 action,所以你不需要关心你要处理的 action 具体的 type 是什么。

reducers里面的方法是同步的,并且是纯函数

  1. export default {
  2. name: 'app',
  3. initialState: {
  4. num : 0
  5. },
  6. + reducers: {
  7. + add(state, data) {
  8. + return state + data
  9. + }
  10. + }
  11. };

effects

所谓的 effects 就是 Redux 的异步 action(async actions)。在函数式编程中,effect 表示所有会与函数外部发生交互的操作。在 Redux 的世界里,异步 action 显然是 effect
effect 不会直接更新 Redux state,通常是在完成某些异步操作(比如 AJAX 请求)之后,再调用其他的“同步 action” 来更新 state。
reducers 对象类似,你在 effects 中定义的所有方法都会以相同名称添加到 actions.<modelName> 上,调用这些方法便会调用 effects 你定义的那些方法。

  1. export default {
  2. name: 'app',
  3. initialState: {
  4. num : 0
  5. },
  6. reducers: {
  7. add(state, data) {
  8. return state + data
  9. }
  10. },
  11. + effects: {
  12. + async myEffect(data, getState) {
  13. + const res = await Promise.resolve(data)
  14. + actions.app.add(res)
  15. + }
  16. + }
  17. };

执行上述代码,actions.app 就会拥有两个方法:actions.app.addactions.app.myEffect
调用 actions.app.myEffect,就会调用 effects.myEffect,简单得不能再简单。

在 effects 中定义的方法接收两个形参:

  1. data - 调用 actions.<modelName> 上的方法时所传递的 data,可选。
  2. getState - 实际上就是 store.getState,返回当前 action 被 dispatch 前的 store 的数据,同样是可选的。

model 注册到 store

  1. import mirror from 'mirrorx'
  2. import model from './model.js'
  3. mirror.model(model)

model 与 UI 组件双向绑定

  1. import mirror, { connect } from 'mirrorx'
  2. import model from './model.js'
  3. mirror.model(model)
  4. import App from './container.js'
  5. const ConnectedApp = connect(state => state.app)(App)
  6. export default ConnectedApp