什么是 Selector ?
在介绍 Reselect
的之前,我们首先要知道 Selector
这个概念,那么什么是 Selector
呢?我看到一种很形象的说法:想象一下你去便利店买可乐,你给店员说要可口可可,这时候店员就去给你拿一罐可口可乐,这时候,店员其实就是充当了 Selector
的角色,店员知道如何从各种商品里拿到你要的可口可乐,具体来说 Selctor
有以下特点:
Selector
知道从哪里,以及如何去获取数据的子集Selector
会返回数据的子集 用代码来表示就是
什么是 Reselect ?
Reselect
作为一个配合 Redux
(或其他)的 使用的一款轻量型的状态选择库:
- 可以计算派生数据,从而使初始状态的存储尽可能少。
- 是高效的,只要选择器的参数不发生改变,计算就不会发生。
- 可组合的,选择器可以作为参数传给其他选择器。
为什么需要 Reselector ?
React
有一个原则:能够计算得到的状态一定要计算获得,state
中存储最原始的数据。这样的原则可能会带来不必要的 render
并伴随大量复杂的重复计算(Selector
),从而产生了性能的损耗,而 Reselect
给 Selector
提供了缓存的能力,避免了重复计算,从而提升性能。
开始
安装
# npm
npm install reselect
# yarn
yarn add reselect
官方入门例子
简易的价格计算器,需要缓存的计算属性有:商品总价(subtotalSelector
)、税费(taxSelector
)和总价(totalSelector
)。商品数量(shopItemsSelector
)和纳税(taxPercentSelector
)比例不变,就不会发生重新计算。
createSelector
传入的 inputSelector
返回值会作为最后一个函数参数的传参,以 subtotalSelector
为例子,shopItemsSelector
的返回值会作为 items
传入。
import { createSelector } from 'reselect';
const shopItemsSelector = (state) => state.shop.items;
const taxPercentSelector = (state) => state.shop.taxPercent;
const subtotalSelector = createSelector(shopItemsSelector, (items) =>
items.reduce((subtotal, item) => subtotal + item.value, 0),
);
const taxSelector = createSelector(
subtotalSelector,
taxPercentSelector,
(subtotal, taxPercent) => subtotal * (taxPercent / 100),
);
const totalSelector = createSelector(
subtotalSelector,
taxSelector,
(subtotal, tax) => ({ total: subtotal + tax }),
);
const exampleState = {
shop: {
taxPercent: 8,
items: [
{ name: 'apple', value: 1.2 },
{ name: 'orange', value: 0.95 },
],
},
};
console.log(subtotalSelector(exampleState)); // 2.15
console.log(taxSelector(exampleState)); // 0.172
console.log(totalSelector(exampleState)); // { total: 2.322 }
应用到项目
拉出我们之前的 列表项目 进行一下改造。当前页面上的数据是通过 state.items
和 state.byId
计算得到的一组对象数组,页面上的任何状态(state.search
)发生变化,都会引起列表数据的重计算(get data source
),看动图 ⬇️
那么,就这点来优化一下。
安装 reselect
,引入 { createSelector }
。
import { createSelector } from 'reselect';
创建 dataSourceSelector
函数,监听 items
和 byId
。
const getItems = (state) => state.items;
const getById = (state) => state.byId;
const dataSourceSelector = createSelector(getItems, getById, (items, byId) => {
console.log('reselect: get data sourc!');
if (!items) return [];
return items.map((id) => byId[id]);
});
在组件和 Redux 连接时,传入新属性 dataSource
,并取代原来的 getDataSource
方法。
const mapStateToProps = function (state) {
return {
...state.table,
dataSource: dataSourceSelector(state.table),
};
};
完成 ✅,除了 items
和 byId
外的其他状态的变化,不再重新计算列表数据。
完整代码,在这里