项目的优化方案 immutable实战

优化方案都会基于 shouldComponentUpdate

总结起来就是:想在对 React 组件进行性能优化时,需要检测 state 或 props 的变化来判断是否需要 render, 如果使用浅比较,存在更新对线属性时引用没变的问题,但只要解决这个问题,浅比较依然是好方案。 因此 immutable 的出现解决的就是有变化就返回新引用,所以浅比较 + immutable 就是性能优化的利器。 后面出现的 Immer 是比 immutable 更好的方案

优化方案一:PureComponent (memo)进行浅层比较

PureComponent 浅比较部分的核心代码:

function shallowEqual(objA: mixed, objB: mixed): boolean {

    // 下面的 is 相当于 === ,只是对 + 0 和 - 0,以及 NaN 和 NaN 的情况进行了特殊处理
    // 第一关:基础数据类型直接比较出结果
    if (is (objA, objB)) {
        return true;
    }

    // 第二关:只要有一个不是对象数据类型就返回 false
    if (
        typeof objA !== 'object' ||
        objA === null ||
        typeof objB !== 'object' ||
        objB === null
    ) {
        return false;
    }

    // 第三关:在这里已经可以保证两个都是对象数据类型,比较两者的属性类型
    const KeysA = Object.keys(objA);
    const KeysB = Object.keys(objB);

    if (KeysA.length !== KeysB.length) {
        return false;
    }

    // 第四关;比较两者的属性是否相等,值是否相等
    for (let i = 0; i < KeysA.length; i++) {
        if (
            !hasOwnProperty.call(objB, KeysA[i]) ||
            !is (objA [KeysA [i]], objB [KeysA[i]])
        ) {
            return false;
        }
    }

    return true;
}

这里只是浅比较,在下面的情况下会判断失灵

// 调用 state.a.push("2")
state: {a: ["1"]} -> state: {a: ["1", "2"]}

上面情况中 a 数组已经发生了改变,但是浅层比较会表示没有改变,因为数组的引用没有变

一旦属性的值为引用类型的时候,浅比较就失灵了

由于 JS 引用赋值的原因,这种浅比较方式仅仅适用于无状态组件或状态数据非常简单的组件,对于大量的应用型组件,它是无能为力的

优化方案二:shouldComponentUpdate 中进行深层对比

为了解决方案一带来的问题,我们现在不做浅层对比了,我们把 props 中所有的属性和值进行递归比对

function deepEqual(objA: mixed, objB: mixed): boolean {

    // 第一关:保证两者都是基本数据类型,基础数据类型直接比较出结果
    // 对象类型就不比了
    if (objA == null && objB == null) return true;
    if (
        typeof objA !== 'object' &&
        typeof objB !== 'object' &&
        is (objA, objB)
    ) {
        return true;
    }

    // 第二关:只要有一个不是对象数据类型就返回 false
    if (
        typeof objA !== 'object' ||
        objA === null ||
        typeof objB !== 'object' ||
        objB === null
    ) {
        return false;
    }

    // 第三关:在这里已经可以保证两个都是对象数据类型,比较两者的属性数量
    const KeysA = Object.keys(objA);
    const KeysB = Object.keys(objB);

    if (KeysA.length !== KeysB.length) {
        return false;
    }

    // 第四关:比较两者的属性是否相等,值是否相等
    for (let i = 0; i < KeysA.length; i++) {
        if (
            !hasOwnProperty.call(objB, KeysA[i]) ||
            !is (objA [KeysA [i]], objB [KeysB[i]])
        ) {
            return false;
        } else {
            if (!deepEqual(objA[KeysA[i]], objB[KeysA [i]])) {
                return false;
            }
        }
    }

    return true;
}

当访问到对象的属性值的时候,将属性值再进行递归对比,这样就达到了深层对比的效果

但是如果属性有一万条的时候,只有最后一个属性发生了改变的极端情况下,我们就迫不得已将一万条属性都遍历,这是浪费性能的

优化方案三:immutable 数据类型 + SCU (memo) 浅层对比

回到问题的本质,无论是直接用浅层对比,还是进行深层对比,我们最终是想知道组件的 props 或 state 数据有无发生改变

这样的情况下:immutable 数据应运而生

什么是 immutable 数据?有什么优势?

immutable 数据是一种利用结构共享形成的持久化数据结构,一旦有部分被修改,将会返回一个全新的对象,并且原来相同的节点会直接共享

immutable 对象数据内部采用的是多叉树的结构,凡是有节点被改变,那么它和与它相关的所有上级节点都更新

只更新父节点,比直接对比所有的属性强太多,并且更新后返回了一个全新的引用,即使是浅对比也能感知到数据的变化

因此,采用 immutable 既能够最大效率地更新数据结构,又能够和现有的 PureComponent, memo 顺利对接,感知到状态的变化,是提高 React 渲染性能的极佳方案

immutable 对象和 JS 对象要注意转换,不能混用,这个注意适当的时候调用 toJS 或者 fromJS 即可

项目中涉及的 immutable 方法

  • fromJS

它的功能是将 JS 对象转换为 immutable 对象

会经常在 redux 的 reducer 文件中看到这个 api,是 immutable 库当中导出的方法

import {fromJS} from 'immutable'

const immutableState = fromJS({
    count: 0
})
  • toJS

和 formJS 功能相反,用来将 immutable 对象转换为 JS 对象

但是这个方法并没有在 immutable 库中直接导出,而是需要让 immutable 对象调用

const jsObj = immutableState.toJS();
  • get / getIn

用来获取 immutable 对象属性

// JS 对象
let jsObj = {a: 1};
let res = jsObj.a;
// immutable 对象
let immutableObj = fromJS(jsObj);
let res = immutableObj.get ('a')

// JS 对象
let jsObj = {a: {b: 1}};
let res = jsObj.a.b;
// immutable 对象
let immutable = fromJS(jsObj);
let res = immutableObj.getIn (['a', 'b']); // 注意传入的是一个数组
  • set

用来对 immutable 对象的属性赋值

let immutable = fromJS ({a: 1});
immutable.set ('a', 2);
  • merge

新数据与旧数据的对比,旧数据中不存在的属性直接添加,旧数据中已存在的数据用新数据中的覆盖

let immutableObj = fromJS ({a: 1});
immutableObj.merge({
    a: 2,
    b: 3
}); // 修改了 a 属性, 增加了 b 属性

最后更新于