优化方案都会基于 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 方法
它的功能是将 JS 对象转换为 immutable 对象
会经常在 redux 的 reducer 文件中看到这个 api,是 immutable 库当中导出的方法
复制 import {fromJS} from 'immutable'
const immutableState = fromJS({
count: 0
})
和 formJS 功能相反,用来将 immutable 对象转换为 JS 对象
但是这个方法并没有在 immutable 库中直接导出,而是需要让 immutable 对象调用
复制 const jsObj = immutableState.toJS();
用来获取 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']); // 注意传入的是一个数组
用来对 immutable 对象的属性赋值
复制 let immutable = fromJS ({a: 1});
immutable.set ('a', 2);
新数据与旧数据的对比,旧数据中不存在的属性直接添加,旧数据中已存在的数据用新数据中的覆盖
复制 let immutableObj = fromJS ({a: 1});
immutableObj.merge({
a: 2,
b: 3
}); // 修改了 a 属性, 增加了 b 属性