美文网首页
redux store设计

redux store设计

作者: new_xd | 来源:发表于2018-04-10 01:19 被阅读0次

store用于存储应用数据,表达应用状态。设计store的步骤大致有两步:

  1. 梳理应用数据都有哪些
  2. 设计这些数据在store中的组织结构

梳理数据

首先说明一下,这里说的数据,有时可能包含一些状态。
数据大概有几类:

  1. 界面需要展示的数据
  2. 为了获取1中的数据,而需要的数据
  3. 界面相关的状态数据
  4. 其他

第一类不用说肯定要放到store里,指需要在界面展示的业务数据
第二类是可能会让人迷惑的,比如请求分页数据时的参数,请求到第几页了
第三类指正在加载,下拉刷新中等界面动画之类的进行状态,或者是用户的交互状态,比如用户是否是第一次打开界面,或者有没有点击过某个按键
第四类比较特殊,我遇到一个就是数据的初始化状态,即数据是否已经初始化

其他的情况暂时没有遇到

我理解的判断一个数据是否应该放入store的方法是:
判断这个数据所代表的含义是否和界面状态无关,如果和界面无关就放入store,有关就单独维护。

比如 分页的页数代表的是业务数据的页数和界面无关,就放入store,
加载中代表的是界面正在laoding,和界面有关,就不放入store
用户是否点击过某个按钮也是界面上的操作,就不放入store
初始化状态是代表数据是否有初始化,和界面无关,就放入store

设计store结构

store的结构都是树状结构
大致的原则是

  1. 按模块划分,比如同一个界面相关的数据放一个树节点上
  2. 尽量扁平,比如一级节点是模块,二级节点就是具体数据
  3. 显式声明使用不变量类型, 比如直接用HashPMap TreePVetor(pcollection库中),而不用Map List

按模块划分是为了方便维护,不同的模块相互不会冲突,也方便不同的模块监听数据
扁平是为了减少嵌套的层级,数据状态一目了然
使用不变量是基本要求,而显示声明类型,是让使用数据的人,可以明确的知道这个是不能修改的

细节

  1. 是否需要初始化状态 看情况,主要是为了解决耗时的初始化产生的前后界面不一致
  2. 叶子节点的数据需要注意,如果是复杂的数据结构,比如自定义类,或者HashPMap,在使用的时候需要考虑空指针
  3. 到底是把复杂的数据扁平化用多个叶子节点,还是用一个复杂数据结构和一个叶子节点,各有利弊,扁平化会使每个reducer的逻辑简单,但是代码较大,而且同一个action可能有好多reducer都要处理,有可能漏reducer处理,一个叶子节点,这个reducer的逻辑会很复杂,但是代码相对集中,如果你有能力有条理的写复杂代码可以用一个叶子节点,如果不行,就扁平化,每个reducer的代码逻辑都很简单,容易上手。

实例分析

背景

我们的界面是多种数据一起刷新,由于接口是复用的,就没有新定义接口,在刷新时是单独调用每个数据接口获取数据,结果就有三种:全部成功、部分成功、全部失败,需求是有数据就展示数据,有数据有失败的时候,Toast提示失败,全部失败时,界面展示异常界面。

设计

因为数据请求失败,是应用数据的状态,就把这个数据放入store了,一般失败都有错误码和错误提示信息,我就把这两个字段都放入store了。
类似下面

{
  "status" : 0,
  "msg" : "Success"
}

界面绑定数据的逻辑如下

if(hasData()) {
  // show data
  if(status != 0) {
    // show Toast with msg
  }
} else {
  // show error view with status
}

第一个bug

在每次刷新单个数据时,如果一直是相同的错误,status和msg不会变,导致只有第一次刷新提示失败,后续刷新就没有错误提示了。
我当时觉得这个问题在于status和msg没有变,所以不会触发数据变化监听,于是我修改了store

{
  "status" : 0,
  "msg" : "Success",
  "counter" : 0
}

每次reducer处理,如果是失败,counter就加1,让数据产生变化,从而触发监听,进行错误提示

第二个bug

每次界面(Fragment)重新加载,重新绑定数据时,如果原来的状态是有部分数据,有失败,就会在Fragment每次启动时,弹Toast。
为什么会这样呢?我当时觉得原因在status和msg其实是上次的请求失败,并不是当下的请求失败,所以我在Fragment里记录counter,绑定数据前,先读取一次,在绑定时做判断,逻辑如下

// 在绑定之前 先获取counter
int msgCounter = counter;

// 绑定逻辑
if(hasData()) {
  // show data
  if(status != 0 && msgCounter != counter) { // counter值不一样 说明这是新的错误,应该提示
    // show Toast with msg
    msgCounter = counter
  }
} else {
  // show error view with status
}

反思

这两个bug,我用这种方式是解决了,但是我的解决方式对吗?

先看第二个bug,status和msg是上次请求的结果,但是实际上数据也是上次请求的结果,在不重新刷新的情况下,上一次请求的数据,就是当下的数据,绑定当下的数据在逻辑上没有问题。

但是为什么会出bug呢,关键在于弹错误提示其实是一次需求,不需要多次弹,但是状态记录在store里,导致每次绑定都会弹Toast。

我开始反思status和msg到底应该不应该放入store,按照前面说的判断方法,异常状态这个数据代表的业务含义就是应用的状态,比如没有网络,退出登录,其实和界面无关,所以应该放入store,没有错。

然后我开始注意到其实我判断异常状态需要的只有status一个字段,没有msg字段,状态判断不会有任何问题,我发现msg字段只是因为我们一直一来错误码和错误信息是放一起的,所以我才把msg字段放入store的。

那我们单独对msg字段进行判断,msg字段表示的业务含义是错误状态在界面上的提示信息,它是和界面相关的,没有界面,也就不需要展示,就不需要msg字段,所以msg字段不应该放入store!
msg字段其实属于前面说的第三类数据:界面相关的状态数据。

那需求又该怎样实现呢?前面的两个bug又该如何修改呢?

// Store结构
{
  "status" : 0
}
// 刷新数据
dispatcher.dispatch(new RrefreshAction(new RefreshListener(){
  void refreshResult(String msg){
    // 如果msg不为空,即刷新有失败的情况,需要弹Toast
    // show Toast with msg
  }
}));

// 数据绑定
if(hasData()) {
  // show data
} else {
  // show error view with status
}

这样修改之后,前面两个bug自然就不存在了

相关文章

网友评论

      本文标题:redux store设计

      本文链接:https://www.haomeiwen.com/subject/bfawhftx.html