Redux进阶(一)

State的不可变化带来的麻烦

在用Redux处理深度复杂的数据时会有一些麻烦。因为js的特性,咱们知道当对一个对象进行复制时其实是复制它的引用,除非你对这个对象进行深度复制。Redux要求你每次你返回的都是一个全新的State,而不是去修改它。这就要求咱们要对原来的State进行深度复制。这每每带来复杂的操做(查找,合并)。一种简单的状况是经过扩展符号或者Object.assign来处理:javascript

return {
    ...state,
    data: {
        ...state.data,
        id: 5
    }
}

这种方式在处理简单的数据来讲是比较方便的,可是若是遇到更深一级的数据结构时会显得很无力,并且代码会变得冗长。不只仅如此,当咱们要处理一个包含着对象的数组时,咱们要怎么办才好呢?我想,除了深度复制数组而后修改新的数组以外大概没有其余的方法了吧?并且很重要的一点是,若是你对原来整个数组进行了复制,那么绑定了数据的UI会自动渲染,即便它们的数据没有发生变化,简单来讲,就是你修改了某一条表格数据,可是界面上整个表格被从新渲染了:html

const TablesSource = {
    query: 'tables',
    tableId: 10,
    data: [{
        key: 11,
        name: '胡彦斌',
        age: 32,//我要修改这里,要复制整个数组后修改新的吗?
        address: '西湖区湖底公园1号'
    }, {
        key: 12,
        name: '胡彦祖',
        age: 42,
        address: '西湖区湖底公园1号'
    }]
};

Redux官方文档中提到了一种解决方案,即范式化数据:归纳起来就一句话:减小层级,惟一id索引,用后端建表的方法构建咱们的数据结构。其中最重要原则无非是扁平化和关联性。最终咱们须要将数据形式转化成如下格式:前端

{
  "entities": {
    "bykey": {
      "11": {
        "key": 11,
        "name": "胡彦斌",
        "age": 32,
        "address": "西湖区湖底公园1号"
      },
      "12": {
        "key": 12,
        "name": "胡彦祖",
        "age": 42,
        "address": "西湖区湖底公园1号"
      }
    },
    "table": {
      "10": {
        "query": "tables",
        "tableId": 10,
        "data": [
          11,
          12
        ]
      }
    }
  },
  "result": 10
}

按照卤煮的理解,范式化数据无非就是给对象瘦瘦身,再深的层级,咱们也尽可能将它们扁平化,这样会减小咱们对State的查找带来的性能消耗。而后是创建索引表,标识每组数据之间的联系。那么怎么样才能获得咱们想要的数据呢?java

normalizr方法使用指南

官方最荐normalizr模块,它的用法仍是须要时间的去研究的。下面咱们就以上面的数据为示例,说明它的用法:git

$ npm i normalizr -S //下载模块

.........

import {normalize, schema} from 'normalizr';//平常导入,没问题
//原始数据
const TablesSource = {
    query: 'tables',
    tableId: 10,
    data: [{
        key: 11,
        name: '胡彦斌',
        age: 32,
        address: '西湖区湖底公园1号'
    }, {
        key: 12,
        name: '胡彦祖',
        age: 42,
        address: '西湖区湖底公园1号'
    }]
};

//建立实体,名称为bykey,咱们看到它的第二个参数是undefined,说明它是最后一层级的对象
const bykey = new schema.Entity('bykey', undefined, {
    idAttribute: 'key'
});
//建立实体,名字为table,索引是tableid。
const table = new schema.Entity('table', {
    data: [bykey] //这里须要说明这些实体的关系,意思是bykey原来table下面的是一个数组,他对应的是data数据,bykey将会取这里的数据创建一个以key为索引的对象。
}, {
    idAttribute: 'tableId'//以tableId为为索引
});

const normalizedData = normalize(TablesSource, table);//生成新数据结构

说明:new schema.Entity的第一个参数表示你建立的最外层的实体名称,第二个参数是它和其余新建立的实体的关系,默认是最小的层级,即它只是最后一层,不包含其余层级了。第三个参数里面有个idAttribute,指的是以哪一个字段为索引,默认是"id",它也能够是个参数,返回你本身构造的惟一值,记住,是惟一值。按照这样的套路,你能够随意构建你想要的扁平化数据结构,不管源数据的层级有多深。咱们最终都会获得但愿的数据结构。github

{
  "entities": {
    "bykey": {、、实体名称
      "11": {//咱们以前设置的惟一所用key
        "key": 11,
        "name": "胡彦斌",
        "age": 32,
        "address": "西湖区湖底公园1号"
      },
      "12": {
        "key": 12,
        "name": "胡彦祖",
        "age": 42,
        "address": "西湖区湖底公园1号"
      }
    },
    "table": {//实体名
      "10": {、、惟一所用tableid
        "query": "tables",
        "tableId": 10,
        "data": [ //data变成了储存key值索引的集合了!由于在以前咱们说明了两个实体之间的关系 data: [bykey]
          11,
          12
        ]
      }
    }
  },
  "result": 10//这里一样储存着table实体里面的索引集合 normalizr(TableSource, table)
}

github上有详细的官方文档可供查找,卤煮在此只是简单的说明一下用法,诸位能够仔细观察文档上的用法,不须要多少时间就能够熟练掌握。到此为止,万里长城,终于走完了第一步。数据库

如何将范式化数据后再次转化

什么?好不容易转化成本身想要的数据结构,还须要再次转化吗?很遗憾告诉你,是的。由于范式化后的数据只便于咱们在维护Redux,而界面业务渲染的数据结构每每跟咱们处理后的数据是不同的,举个栗子:bootstrap或者ant.design的表格渲染数据结构是这个样的:npm

const dataSource = [{
  key: '1',
  name: '胡彦斌',
  age: 32,
  address: '西湖区湖底公园1号'
}, {
  key: '2',
  name: '胡彦祖',
  age: 42,
  address: '西湖区湖底公园1号'
}];

于是在界面引用State上的数据时,咱们须要一个中介,把范式化的数据再次转化成业务数据结构。我相信这个步骤十分简单,只须要写一个简单的转换器就好了:redux

const transform = (source) => {
    const data = source.entities.bykey;
    return Object.keys(data).map(v => data[v]);
};

const mapStateToProps = (state,ownProps) => ({table: transform(state)})

export default connect(mapStateToProps)(view)

若是你在mapStateToProps里面断点调试,你会发现每一次dispatch都会强行执行mapStateProps方法保证对象的最新状态(除非你引用的是基础类型数据),所以,无论界面的操做是如何,被connect数据都会被强行执行一次,虽然界面没有变化,可是显然,js的性能会有折扣,尤为是对深度对象的复杂处理。所以,官方推荐咱们建立可记忆的函数高效计算Redux Store里面的衍生数据。bootstrap

Reselect方法使用指南

//缓存data里面的索引
const reNormalDataSource = (state, props) => state.app.entities.table['10'].data;
//缓存bykey里面对得基础数据
const reNormal = (state, props) => state.app.entities.bykey;
// 缓存计算结果
const createNormalTableData = createSelector([reNormalDataSource, reNormal], (keys, source) => keys.map(item => source[item]));
//每次当mapStateToProps从新执行时,会储存上次计算的结果,它只会从新计算变化的数据,其余非相关变化不作计算
const mapStateToProps = (state, own) => ({source: createNormalTableData(state)});

我在这里作了个耍了点花样,你能够看到,我是按照table.data这个数组来查找界面业务数据的。这种操做可使得咱们只须要关心table.data这个简单的一维数组,在删除或者添加一条数据的时候显得尤其有用。

咱们最后为了计算state,引用了dot-prop-immutable模块,他是immutable的扩展,对于数据计算很是高效。我接着使用了另一个dot-prop-immutable-chain模块,它增长了dot-prop-immutable的链式用法。关于dot-prop-immutable的用法卤煮再也不详细说明,在后面给出的例子中一眼就能看明白,并且官方文档上也有详细说明。下面咱们经过一个表格增删查改来实际展现咱们此次的解决方案。

import {normalize, schema} from 'normalizr';
import dotProp from 'dot-prop-immutable-chain';

const reducer = (state = normalizedData, action) => {
    switch(action.type) {
        //修改一条数据
        case 'EDITOR':
            return dotProp(state).set(`entities.bykey.${action.key}.age`, action.age).value();
        //添加一条数据
        case 'ADD':
            const newId = UID++;
           return dotProp(state).set(`entities.bykey.${newId}`, Object.assign({}, model, {key: newId}))//添加一条新数据
               .merge(`entities.table.10.data`, [newId]).value();//新数据的data中的引用
        //删除一条数据   
        case 'DELETE':
            const index = state.entities.table['10'].data.indexOf(Number(action.key));
            //能够看到,因为咱们界面数据是根据data里面的项来决定的,所以咱们只须要处理data这个简单的一维数组,而这显然要容易维护得多
            return dotProp(state).delete(`entities.table.10.data.${index}`).value();
    }
    return state;
};

瞧,咱们展现了整个reducer,相信它已经变得容易维护得多了,而且因为使用了范式化数据结构以及immutable的扩展模块,不只仅提高了计算性能,减小界面的的渲染,并且还符合ReduxState不可修改的原则。

结束语

React+Redux组合在实际应用过程当中须要优化的地方还不少,这里只是简单展现其中的一个小点。虽然在计算dom界面变化时React已经作得足够好,但并不意味着咱们能够不用为界面渲染问题背锅,React肩负了多数界面更新计算的任务,而让前端开发人员更多地去处理数据,所以,咱们能够在这里层多花点时间把项目作好。

参考资料

 Redux中文文档

把你的redux store 组织成数据库形式

normalizr在GitHub上的地址

reselect在GitHub上的地址

dot-prop-immutable在GitHub上的地址

相关文章
相关标签/搜索