
依赖关系问题
这是一个相对复杂页面的结构,如图上半部分,箭头说明了各模块数据更新影响关系:
- A 直接或间接影响 B、C、D、E;
- C 直接或间接影响 D、E;(C 的激活项作为 D、E 表格的数据源请求参数)
- D 直接影响 E;(D 表格的激活项作为 E 表格的数据源请求参数)
如果同时有直接和间接影响的箭头,去掉直接影响的箭头,得到如图下半部分。在这个图中,用户一次交互带来的结果是 A、C、D 模块的其中一个更新,其它模块的更新依赖这三个模块,可以理解成这三个模块与其它模块之间发生了强制绑定的关系,即 A更新则C、B一定更新,C更新则D一定更新,D更新则E一定更新。
理解了上面的依赖关系后,现在需要考虑的是代码该怎么组织的问题。正常来说,每个模块的数据请求应该放到一个独立的 effect 函数里,这个函数里通常包括三个步骤:
- 从 state 中读取请求需要的参数;
- 调用 services ,等待请求响应;
- 同步更新 state
这三个步骤分别对应 select、call、put 操作。这也是我理解的每个函数只负责一件事,不掺杂其他逻辑。但是,一旦我们要应对上面的复杂的依赖关系时,我们就需要将这些异步操作有序地组织起来。可以使用 dva 提供的 callback API。
类似这样:
export default {
namespace: 'namespace',
state: {},
subscriptions: {
setup ({ dispatch }) {
dispatch({
type: 'query'
})
}
},
effects: {
// 初渲染
* query (_, { put }) {
yield put({
type: 'queryChartsData'
});
yield put({
type: 'queryCardData',
callback: function* () {
yield put({
type: 'queryTableLeft',
callback: function* () {
yield put({
type: 'queryTableRight'
})
}
})
}
});
},
// 筛选变化
* queryFilterChange (_, { put }) {
yield put({
type: 'queryChartsData'
});
yield put({
type: 'queryCardData',
callback: function* () {
yield put({
type: 'queryTableLeft',
callback: function* () {
yield put({
type: 'queryTableRight'
})
}
})
}
})
},
// 可视化数据
* queryChartsData () {
},
// 卡片数据
* queryCardData ({ callback }, { call, put, select }) {
yield select();
yield call();
yield put({
type: 'updateState' // 将 state 中的该筛选变量更新为新数据的第一项
});
if(callback) {
yield callback();
}
},
// 左表格数据
* queryTableLeft () {
},
// .......
reducers: {
updateState (state, { payload }) {
return { ...state, ...payload }
}
}
}
但是,回调带来的问题就是像上面一样,一旦逻辑复杂,代码会很难阅读。最重要的是,我觉得回调并不是解决流程控制这类问题的根本方法,因为不能完全保证传入的回调函数会在目标函数的最后执行,也就不能严格控制两者先后顺序。另外一点,我觉得使用回调来表达一种“充分条件关系”(即A一定导致B)并不严谨,回调函数可传可不传。
所以,应该抛弃回调这种形式,在每个 effect 执行完 updateState 之后,直接执行下一步的代码。或者,为了保留单一职责原则,可以另外增加专门组织 effects 之间逻辑关系的 effect ,使用 yield 控制顺序。例如:
// 筛选变化
* queryFilterChange (_, { put }) {
put({
type: 'queryChartsData'
});
yield put({
type: 'queryCardData',
});
yield put({
type: 'queryTableLeft'
});
yield put({
type: 'queryTableRight'
})
},
总之,造成我滥用 callback 的原因是对 dva 、回调、generator 以及异步概念理解不够深入。
页面生命周期
总结一下页面生命周期里的一般流程:

在页面初渲染的时候一般会请求筛选条件的数据源,有必要的话会更新 filters State,当然,如果各筛选条件默认值确定的情况下,请求数据源和请求列表数据可以同时进行,拿到响应数据后页面渲染;
在用户进行交互的时候会先改变 filters State 再请求 listData State,页面重新渲染;
在页面卸载时,手动清除 state;
组件问题小结

组件大致可以分三类:页面组件、业务组件、通用组件。页面组件负责读取 Models 中的数据、修改 Models、向下层组件分发数据(props);业务组件负责组装好通用组件,封装特定的业务逻辑;通用组件负责处理用户交互;
组件之间的 props 主要由 数据(data)和方法(callback)组成,callback 是用来子组件向父组件通信的,其实就是把子组件中修改 models 的操作都转移到根组件(页面组件)中,与 models 产生联系的只有页面组件,尽量保持数据单向流动。
在之前的代码中,两个页面高度相似的部分像可视化展示部分,没有抽取成业务级的公共组件,造成很多代码重复,维护起来比较困难,在之后的组件设计中要避免这种情况。