背景
在我们传统的开发模式中,没有用到复杂的状态管理,只是应用了组件内部State以及对外Props属性,相对于简单的应用开发是可以满足的,当遇到复杂的应用后,组件内部的状态过多,维护的也就越复杂,组件之间的“信息”传递也就会困难,稍不注意就会出现问题,这也是开发者头疼的事情。Redux、Mobx等状态管理解决方案的出现在技术上解决了开发者的难题,但上手门槛、概念多、样板式代码等问题也随着暴露,“当年的屠龙少年,自己变成了一条龙”。
**
mirrorx — 更简单清晰的解决方案
基于以上实践考虑,我们在ucf-web中选择引入 mirrorx 模型框架来解决这个困扰的问题,它不是横空出世的新物种,它只是在 Redux 之上的衍生解决方案,继承了Redux的单一数据源、数据不可变、纯函数执行等三大原则的优势,并解决了概念多、样板式代码、状态树维护难等问题。
使用 mirrorx ,主要工作在于如何定义 model 模型文件。
定义model
一个基本的model如下这个样子:
/*** 数据模型类*/export default {name: "app",initialState: {},reducers: {updateState(state, data) {return {...state,...data};}},effects: {}};
可以看出仅仅只有4个字段,下面来详细解读一下4个字段具体使用含义。
name
顾名思义,模型的名称,每一个业务对应一个模型通过该字段的定义来找到对应的方法。
要创建 model,必须要指定 name,且为一个合法字符串。name 很好理解,就是 model 的名称,这个名称会用于后面创建的 Redux store 里的命名空间。
假设定义了一个这样的 model:
export default {name: 'app'};
那么最后创建的 Redux store 会是这样的结构:
store.getState();// {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:
export default {name: 'app',+ initialState: {+ num : 0+ }};
得到的 store:
store.getState();// { app: { num : 0 } }
reducers
Mirror app 所有的 Redux reducer 都是在 reducers 中定义的,reducers 对象中的方法本身会用于创建 reducer,方法的名字会用于创建 action type。Mirror 的原则是,一个 reducer 只负责一个 action,所以你不需要关心你要处理的 action 具体的 type 是什么。
reducers里面的方法是同步的,并且是纯函数
export default {name: 'app',initialState: {num : 0},+ reducers: {+ add(state, data) {+ return state + data+ }+ }};
effects
所谓的 effects 就是 Redux 的异步 action(async actions)。在函数式编程中,effect 表示所有会与函数外部发生交互的操作。在 Redux 的世界里,异步 action 显然是 effect。effect 不会直接更新 Redux state,通常是在完成某些异步操作(比如 AJAX 请求)之后,再调用其他的“同步 action” 来更新 state。
和 reducers 对象类似,你在 effects 中定义的所有方法都会以相同名称添加到 actions.<modelName> 上,调用这些方法便会调用 effects 你定义的那些方法。
export default {name: 'app',initialState: {num : 0},reducers: {add(state, data) {return state + data}},+ effects: {+ async myEffect(data, getState) {+ const res = await Promise.resolve(data)+ actions.app.add(res)+ }+ }};
执行上述代码,actions.app 就会拥有两个方法:actions.app.add 和 actions.app.myEffect。
调用 actions.app.myEffect,就会调用 effects.myEffect,简单得不能再简单。
在 effects 中定义的方法接收两个形参:
data- 调用actions.<modelName>上的方法时所传递的 data,可选。getState- 实际上就是store.getState,返回当前 action 被 dispatch 前的 store 的数据,同样是可选的。
model 注册到 store
import mirror from 'mirrorx'import model from './model.js'mirror.model(model)
model 与 UI 组件双向绑定
import mirror, { connect } from 'mirrorx'import model from './model.js'mirror.model(model)import App from './container.js'const ConnectedApp = connect(state => state.app)(App)export default ConnectedApp
