一、这里给出三个思路进行React性能优化
- 使用 shouldComponentUpdate 规避冗余的更新逻辑
- PureComponent || React.memo + Immutable.js
- React.memo 与 useMemo
1、shouldComponentUpdate
React 组件会根据 shouldComponentUpdate 的返回值,来决定是否执行该方法之后的生命周期,进而决定是否对组件进行 re-render(重渲染)。
尽量做浅层对比,想想一种极端的情况,就是在属性有一万条的时候,只有最后一个属性发生了变化,那我们就不得已将一万条属性都遍历。这是非常浪费性能的。
2、PureComponent || React.memo + Immutable.js
1. PureComponent和memo
先来看一下PureComponent 和 memo。 PureComponent 或者 memo将会进行新旧数据的浅层比对。
看一下源码:
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;
}
从源码可以看出,若数据是引用类型,则比较会失效。
2. immutable优势
immutable 数据一种利用结构共享形成的持久化数据结构,一旦有部分被修改,那么将会返回一个全新的对象,并且原来相同的节点会直接共享。
具体点来说,immutable 对象数据内部采用是多叉树的结构,凡是有节点被改变,那么它和与它相关的所有上级节点都更新。
采用 immutable 既能够最大效率地更新数据结构,又能够和现有的 PureComponent (memo) 顺利对接,感知到状态的变化,是提高 React 渲染性能的极佳方案。
3. useMemo
useMemo 控制是否需要重复执行某一段逻辑。
二、实战篇
1. 使用memo和children优化render
假如有这样一个需求:
import React, { useContext, useState } from "react";
const ThemeContext = React.createContext();
const ChildNoReRender = (props) => {
console.log("ChildNoReRender ------ 我是不重新渲染的子组件");
return <div>我是不重新渲染的子组件</div>;
};
function ChildReRender() {
const theme = useContext(ThemeContext);
return (
<>
<div>我是重新渲染的子组件~ {theme}</div>
</>
);
}
export default function App() {
const [theme, setTheme] = useState("YES");
const onChangeComponent = () => setTheme(theme === "YES" ? "No" : "YES");
return (
<>
<button onClick={onChangeComponent}>改变子组件</button>
<ThemeContext.Provider value={theme}>
<ChildReRender />
<ChildNoReRender />
</ThemeContext.Provider>
</>
);
}
这段代码看起来没问题,但是运行起来发现,当点击改变子组件的按钮时候,ChildNoReRender组件也会重新渲染,我们并没有给他传递任何的数据。
本质的原因是React代码至上而下递归更新的,JSX代码会被babel编译为React.createElement()函数调用,从React源码(不熟悉的可以看这篇https://www.yuque.com/linhe-8mnf5/fxyxkm/xekk7b)可以看出每次调用该函数都会生成一个新的props,而从生命周期来看,新的props会引发重新render操作,所以出现了此类的现象。
明白了原理之后,我们来想解决办法。
办法1:抽离改变组件逻辑,使用children来避免当前组件重新调用React.createElement()操作。
import React, { useContext, useState } from "react";
const ThemeContext = React.createContext();
const ChildNoReRender = (props) => {
console.log("我是不重新渲染的子组件");
return <div>我是不重新渲染的子组件</div>;
};
function ChildReRender() {
const theme = useContext(ThemeContext);
return (
<>
<div>我是重新渲染的子组件~ {theme}</div>
</>
);
}
function ThemeApp(props) {
const [theme, setTheme] = useState("YES");
const onChangeComponent = () => setTheme(theme === "YES" ? "NO" : "YES");
return (
<ThemeContext.Provider value={theme}>
<button onClick={onChangeComponent}>改变子组件</button>
{props.children}
</ThemeContext.Provider>
);
}
export default function App() {
return (
<ThemeApp>
<ChildReRender />
<ChildNoReRender />
</ThemeApp>
);
}
办法2:使用memo来做,memo做了浅比较。
import React, { useContext, useState } from "react";
const ThemeContext = React.createContext();
const ChildNoReRender = React.memo((props) => {
console.log("ChildNoReRender ------ 我是不重新渲染的子组件");
return <div>我是不重新渲染的子组件</div>;
});
function ChildReRender() {
const theme = useContext(ThemeContext);
return (
<>
<div>我是重新渲染的子组件~ {theme}</div>
</>
);
}
export default function App() {
const [theme, setTheme] = useState("YES");
const onChangeComponent = () => setTheme(theme === "YES" ? "No" : "YES");
return (
<>
<button onClick={onChangeComponent}>改变子组件</button>
<ThemeContext.Provider value={theme}>
<ChildReRender />
<ChildNoReRender />
</ThemeContext.Provider>
</>
);
}
可以根据自己项目的场景选择合适的解决方案。