React+Redux-Saga+Seamless-Immutable+Reduxsauce后台系统搭建之路(三)

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树:


React+Redux-Saga+Seamless-Immutable+Reduxsauce后台系统搭建之路(三)_第1张图片
总览.png

React+Redux-Saga+Seamless-Immutable+Reduxsauce后台系统搭建之路(三)_第2张图片
远端数据.png

React+Redux-Saga+Seamless-Immutable+Reduxsauce后台系统搭建之路(三)_第3张图片
表格.png

React+Redux-Saga+Seamless-Immutable+Reduxsauce后台系统搭建之路(三)_第4张图片
模态框.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。

REFRENCE:

  • redux-saga中文
  • You Might Not Need Redux
  • Immutable 详解及 React 中实践
  • Trie树(Prefix Tree)介绍

你可能感兴趣的:(React+Redux-Saga+Seamless-Immutable+Reduxsauce后台系统搭建之路(三))