一、这里给出三个思路进行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将会进行新旧数据的浅层比对。

看一下源码:

  1. function shallowEqual (objA: mixed, objB: mixed): boolean {
  2. // 下面的 is 相当于 === 的功能,只是对 + 0 和 - 0,以及 NaN 和 NaN 的情况进行了特殊处理
  3. // 第一关:基础数据类型直接比较出结果
  4. if (is (objA, objB)) {
  5. return true;
  6. }
  7. // 第二关:只要有一个不是对象数据类型就返回 false
  8. if (
  9. typeof objA !== 'object' ||
  10. objA === null ||
  11. typeof objB !== 'object' ||
  12. objB === null
  13. ) {
  14. return false;
  15. }
  16. // 第三关:在这里已经可以保证两个都是对象数据类型,比较两者的属性数量
  17. const keysA = Object.keys (objA);
  18. const keysB = Object.keys (objB);
  19. if (keysA.length !== keysB.length) {
  20. return false;
  21. }
  22. // 第四关:比较两者的属性是否相等,值是否相等
  23. for (let i = 0; i < keysA.length; i++) {
  24. if (
  25. !hasOwnProperty.call (objB, keysA [i]) ||
  26. !is (objA [keysA [i]], objB [keysA [i]])
  27. ) {
  28. return false;
  29. }
  30. }
  31. return true;
  32. }

从源码可以看出,若数据是引用类型,则比较会失效。

2. immutable优势

immutable 数据一种利用结构共享形成的持久化数据结构,一旦有部分被修改,那么将会返回一个全新的对象,并且原来相同的节点会直接共享。
具体点来说,immutable 对象数据内部采用是多叉树的结构,凡是有节点被改变,那么它和与它相关的所有上级节点都更新。

kywj81lbqg.gif

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

3. useMemo

useMemo 控制是否需要重复执行某一段逻辑。

二、实战篇

1. 使用memo和children优化render

假如有这样一个需求:

  1. import React, { useContext, useState } from "react";
  2. const ThemeContext = React.createContext();
  3. const ChildNoReRender = (props) => {
  4. console.log("ChildNoReRender ------ 我是不重新渲染的子组件");
  5. return <div>我是不重新渲染的子组件</div>;
  6. };
  7. function ChildReRender() {
  8. const theme = useContext(ThemeContext);
  9. return (
  10. <>
  11. <div>我是重新渲染的子组件~ {theme}</div>
  12. </>
  13. );
  14. }
  15. export default function App() {
  16. const [theme, setTheme] = useState("YES");
  17. const onChangeComponent = () => setTheme(theme === "YES" ? "No" : "YES");
  18. return (
  19. <>
  20. <button onClick={onChangeComponent}>改变子组件</button>
  21. <ThemeContext.Provider value={theme}>
  22. <ChildReRender />
  23. <ChildNoReRender />
  24. </ThemeContext.Provider>
  25. </>
  26. );
  27. }

这段代码看起来没问题,但是运行起来发现,当点击改变子组件的按钮时候,ChildNoReRender组件也会重新渲染,我们并没有给他传递任何的数据。

image.png

本质的原因是React代码至上而下递归更新的,JSX代码会被babel编译为React.createElement()函数调用,从React源码(不熟悉的可以看这篇https://www.yuque.com/linhe-8mnf5/fxyxkm/xekk7b)可以看出每次调用该函数都会生成一个新的props,而从生命周期来看,新的props会引发重新render操作,所以出现了此类的现象。

明白了原理之后,我们来想解决办法。

办法1:抽离改变组件逻辑,使用children来避免当前组件重新调用React.createElement()操作。

  1. import React, { useContext, useState } from "react";
  2. const ThemeContext = React.createContext();
  3. const ChildNoReRender = (props) => {
  4. console.log("我是不重新渲染的子组件");
  5. return <div>我是不重新渲染的子组件</div>;
  6. };
  7. function ChildReRender() {
  8. const theme = useContext(ThemeContext);
  9. return (
  10. <>
  11. <div>我是重新渲染的子组件~ {theme}</div>
  12. </>
  13. );
  14. }
  15. function ThemeApp(props) {
  16. const [theme, setTheme] = useState("YES");
  17. const onChangeComponent = () => setTheme(theme === "YES" ? "NO" : "YES");
  18. return (
  19. <ThemeContext.Provider value={theme}>
  20. <button onClick={onChangeComponent}>改变子组件</button>
  21. {props.children}
  22. </ThemeContext.Provider>
  23. );
  24. }
  25. export default function App() {
  26. return (
  27. <ThemeApp>
  28. <ChildReRender />
  29. <ChildNoReRender />
  30. </ThemeApp>
  31. );
  32. }

办法2:使用memo来做,memo做了浅比较。

  1. import React, { useContext, useState } from "react";
  2. const ThemeContext = React.createContext();
  3. const ChildNoReRender = React.memo((props) => {
  4. console.log("ChildNoReRender ------ 我是不重新渲染的子组件");
  5. return <div>我是不重新渲染的子组件</div>;
  6. });
  7. function ChildReRender() {
  8. const theme = useContext(ThemeContext);
  9. return (
  10. <>
  11. <div>我是重新渲染的子组件~ {theme}</div>
  12. </>
  13. );
  14. }
  15. export default function App() {
  16. const [theme, setTheme] = useState("YES");
  17. const onChangeComponent = () => setTheme(theme === "YES" ? "No" : "YES");
  18. return (
  19. <>
  20. <button onClick={onChangeComponent}>改变子组件</button>
  21. <ThemeContext.Provider value={theme}>
  22. <ChildReRender />
  23. <ChildNoReRender />
  24. </ThemeContext.Provider>
  25. </>
  26. );
  27. }

可以根据自己项目的场景选择合适的解决方案。