dva 处理复杂页面小结

image.png

依赖关系问题

这是一个相对复杂页面的结构,如图上半部分,箭头说明了各模块数据更新影响关系:

  • 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 函数里,这个函数里通常包括三个步骤:

  1. 从 state 中读取请求需要的参数;
  2. 调用 services ,等待请求响应;
  3. 同步更新 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 以及异步概念理解不够深入。

页面生命周期

总结一下页面生命周期里的一般流程:


image.png

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

组件问题小结

image.png

组件大致可以分三类:页面组件、业务组件、通用组件。页面组件负责读取 Models 中的数据、修改 Models、向下层组件分发数据(props);业务组件负责组装好通用组件,封装特定的业务逻辑;通用组件负责处理用户交互;

组件之间的 props 主要由 数据(data)和方法(callback)组成,callback 是用来子组件向父组件通信的,其实就是把子组件中修改 models 的操作都转移到根组件(页面组件)中,与 models 产生联系的只有页面组件,尽量保持数据单向流动。

在之前的代码中,两个页面高度相似的部分像可视化展示部分,没有抽取成业务级的公共组件,造成很多代码重复,维护起来比较困难,在之后的组件设计中要避免这种情况。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容