React 性能优化:避免重复计算

649 阅读3分钟

什么是 Selector ?

在介绍 Reselect 的之前,我们首先要知道 Selector 这个概念,那么什么是 Selector 呢?我看到一种很形象的说法:想象一下你去便利店买可乐,你给店员说要可口可可,这时候店员就去给你拿一罐可口可乐,这时候,店员其实就是充当了 Selector 的角色,店员知道如何从各种商品里拿到你要的可口可乐,具体来说 Selctor 有以下特点:

  • Selector 知道从哪里,以及如何去获取数据的子集
  • Selector 会返回数据的子集 用代码来表示就是

什么是 Reselect ?

Reselect 作为一个配合 Redux(或其他)的 使用的一款轻量型的状态选择库:

  • 可以计算派生数据,从而使初始状态的存储尽可能少。
  • 是高效的,只要选择器的参数不发生改变,计算就不会发生。
  • 可组合的,选择器可以作为参数传给其他选择器。

为什么需要 Reselector ?

React 有一个原则:能够计算得到的状态一定要计算获得,state 中存储最原始的数据。这样的原则可能会带来不必要的 render 并伴随大量复杂的重复计算(Selector),从而产生了性能的损耗,而 ReselectSelector 提供了缓存的能力,避免了重复计算,从而提升性能。

开始

安装

# 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.itemsstate.byId 计算得到的一组对象数组,页面上的任何状态(state.search)发生变化,都会引起列表数据的重计算(get data source),看动图 ⬇️

reselect-1.gif

那么,就这点来优化一下。

安装 reselect,引入 { createSelector }

import { createSelector } from 'reselect';

创建 dataSourceSelector 函数,监听 itemsbyId

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),
  };
};

完成 ✅,除了 itemsbyId 外的其他状态的变化,不再重新计算列表数据。

ezgif.com-gif-maker (1).gif

完整代码,在这里

React 性能优化