Better Practice
1 .不要改变服务器返回的数据
比如服务器返回一个星期信息,格式为:“0,1,2,3,4,5,6”,代表周日、周一、周二、周三、周四、周五、周六,如果为了方便显示,在把数据存到state里面的时候就改变了它的数据,直接改成了周日、周一、周二、周三、周四、周五、周六,那么如果后面需要再次使用这个数据的时候,很难恢复到它最原始的模样了,这是一件吃力不讨好的事情,所以不要改变服务器返回的数据的格式,把展示格式的任务交给view层来做。
2. MVC
这样一个架构其实很明显地将代码分成了MVC层,理论上来说我们可以把所有的组件都做成无状态组件,这里是View层,把saga作为Controller层,reducer作为Module层。
这样我们整个页面的逻辑都放到了Controller层来实现了,页面与页面之间的逻辑不会经常有重合的地方,所以我认为Controller层的每一个函数可以用组件的功能实现来区分。这样页面中用到的所有相同组件都可以用一个方法来管理。
比如这样一个列表:上面有筛选条件,下面是可翻页的表格。
这种列表出现的频率是非常高的,上面是请求参数,现在展示一个列表,同时这个列表是可以翻页的。输入一些搜索条件并点查询翻页要清零,翻页时要保存搜索条件,再根据后端返回的数据将页码传给表格等一系列复杂逻辑,如果一个功能有十个这样的表格,那这样的逻辑就要重复十次。如果我们把这些逻辑都统一到一个saga里,只通过参数中的status来区分数据,这个status反映到state中就是一个结点(稍后会讲到),可以根据结点的名字来区分不同的表格。
export function* queryListData() {
while (true) {
const action = yield take(sagaTypes.QUERY_LIST_DATA)
const { payload } = action
const state = yield select()
let { status, filterParams, pagination } = payload
let defaultFilterParams = {}
let params = {}
let defaultPagination = {
perpage: 10,
pageSize: 10,
curpage: 1,
current: 1,
total: 0
}
if (state.detail.list[status]) {
defaultFilterParams = state.detail.list[status].filterParams
// defaultPagination = state.detail.list[status].pagination
}
if (pagination && pagination.pageSize) {
pagination.perpage = pagination.pageSize
}
if (pagination && pagination.current) {
pagination.curpage = pagination.current
}
filterParams = {
...defaultFilterParams,
...filterParams
}
pagination = {
...defaultPagination,
...pagination
}
params = {
...filterParams,
...pagination,
type: 'page'
}
yield put({
type: types.SET_IN,
payload: { path: ['list', status, 'filterParams'], value: filterParams }
})
yield put({
type: sagaTypes.QUERY_DATA,
payload: {
status: payload.status,
params
}
})
}
}
3. 状态树的设计
归根结底,我的设计就是组件和数据是一一对应的关系,每个组件自己接入想要的数据,然后抽出公共的方法,这样的来代码结构会非常清晰,state树也只要稍加解释就可以让所有人都能明白其含义,下面是我的state树:
总览.png
远端数据.png
表格.png
模态框.png
4. 流程控制
在项目初始化的时候,我的做法是把能load的数据全部都load进来,通过saga可以通过fork无阻塞的执行这些操作,但是组件已经全部无状态化了,load数据之后会改变state,又会重新执行一些这个函数组件,又会重新load数据...无限循环。
saga有一个非常好的功能,监听未来的 action,具体的使用方法是这样的:
// 这是一个load全部数据的函数
export function* watchInit() {
while (true) {
const action = yield take(sagaTypes.INIT)
....拉取远端数据的代码
yield take(sagaTypes.CLEAR)
...一些后续操作
}
}
就是如果发起了一个type为sagaTypes.INIT的action,在没有发起type为sagaTypes.CLEAR的action之前,watchInit将不再执行。我将之称为锁,这样就在无状态组件中模拟实现了componentDidMount里拉数据的过程。但是这种做法的优点在于,试想你需要实现一个功能,有N个页面,每一个页面都需要依赖远端数据,又必须要在每一个页面随时可以点击刷新。如果我们在每一个子页面的componentDidMount方法里都拉一遍数据,那么每进入一个页面中的时候势必会重新拉一遍数据。而saga的实现方法可以做到只要不解锁,就不会重新拉数据,也就是说,虽然每一个页面都会发起sagaTypes.INIT的action,但是只要流程没有走完,就只会拉取一次数据。
5. 总结
if you trade something off, make sure you get something in return. 刚开始使用这一套架构的时候,我也走了很多弯路,因为这样一套工具使用起来,实现一个功能的时候,往往需要修改三个文件,会导致代码量激增。后来总结出来这样一套模式以后,不仅减少了我90%的代码量,而且我相信这样的模式是可以用于任何一个后台页面的。模式一旦确定下来,这样就只剩下很多重复性的工作,那么是不是可以做一个自动生成页面的工具呢?这就是tita。
网友评论