📖 “停止 React 中不必要的重新渲染组件!!”的历史记录

2025-05-27

📖 “停止 React 中不必要的重新渲染组件!!”的历史记录

很久很久以前……

💎 生成类组件

🚩 纯组件()

比较新旧 props/state,如果它们之间存在差异,则组件渲染

比较??但是如何比较它们呢?

<< React 中渲染的情况 >>

  1. 状态改变
  2. 父组件渲染
  3. 道具变化
  4. shouldcomponentUpdate 函数返回 true (稍后我会解释)
  5. 强制更新

对于数字 1 和 2,React 决定是否通过浅比较进行渲染

什么是浅比较?

首先,我们需要了解 一下这个网站是什么

图片描述

  1. 通过引用传递(浅拷贝)
    如果你将咖啡倒入复制的杯子中,那么原始杯子也会被装满(因为两个数据都在相同的内存分配空间中)

  2. 按值传递(深层复制)
    如果你将咖啡倒入复制的杯子中,原始杯子仍然是空的

在 Javascript 中,原始数据类型(String、Number、Bigint、Boolean、Undefined、Symbol)通过值传递,而 Object、Array 通过引用传递

老实说,与原始数据类型进行比较并不困难,但我们需要关心与 Object 的比较

对象引用的情况也一样

import shallowCompare from 'react-addons-shallow-compare';

const a = { country: "poland", country2: "japan" }
const b = a

console.log(shallowEqual(a, b))
// true
Enter fullscreen mode Exit fullscreen mode

对象引用的情况不同

  1. 非嵌套对象
import shallowCompare from 'react-addons-shallow-compare';

const a = { country: "poland", country2: "japan" }
const b = { country: "poland", country2: "japan" }

console.log(shallowEqual(a, b))
// true
Enter fullscreen mode Exit fullscreen mode
  1. 嵌套对象
import shallowCompare from 'react-addons-shallow-compare';

const a = {
  country: "poland",
  coountry2: {
    city1: "tokyo",
    city2: "osaka"
  }
}

const b = {
  country: "poland", // country is primitive type, scalar data is the same -> true
  country2: { // country2 is object, so reference is different -> false
    city1: "tokyo",
    city2: "osaka"
  }
}

console.log(shallowEqual(a, b))
// ⭐ false
Enter fullscreen mode Exit fullscreen mode

🚩 shouldComponentUpdate()

👦 所以所有组件都是纯组件就没问题了,不是吗?
👩‍💻 不行,因为比较新旧 state/props 的成本很高
👦 那我该怎么办?
👩‍💻 直接通过“shouldComponentUpdate()”自行决定比较条件就行了。

实际上 PureComponent 就像是由某人(可能是 Facebook 公司的某个人)通过 shouldComponentUpdate() 实现的组件

// something like that
class PureComponent extends React.Component {
    shouldComponentUpdate(nextProps, nextState) {
        return !(shallowEqual(this.props, nextProps) && shallowEqual(this.state, nextState));
    }
    
}
Enter fullscreen mode Exit fullscreen mode

💎 函数组件生成

2022年我们处于这一代

🚩 React.memo

它就像 PureComponent() + shouldComponentUpdate()

// if new props changes, this component will be rendered
const Button = React.memo(props => {
    return <div>{props.value}</div>
})
Enter fullscreen mode Exit fullscreen mode
// if you put second argument, it is like shouldComponentUpdate()
const Button = React.memo(
    props => {
        return <div>{props.value}</div>
    },
    (nextProps, prevProps) => {
        return nextProps.value === prevProps.value
    }
)
Enter fullscreen mode Exit fullscreen mode

🚩 useMemo

👦 useMemo 是什么?它和 React.memo 一样吗?
👩‍💻 不一样,其实很像。React.memo 是 React 16.6 版本新增的,之后 React hook 16.8 版本也新增了 useMemo。👦
啊哈
👩‍💻 useMemo 只会在 props 发生变化时渲染,因为它会记住计算结果。

// when only "products props" changes, this component renders
const Component: React.FC = ({ products }) => {
    const soldoutProducts = React.useMemo(() => products.filter(x => x.isSoldout === true), [products])
}
Enter fullscreen mode Exit fullscreen mode

🚩 useCallback

当父组件将函数的 props 传递给子组件时,会创建一个新的函数(实际上函数只是对象之一)。
因此,子组件会识别出这个新的函数与旧函数不同,然后悲伤地重新渲染。

↓ 子组件/父组件之间的对话

👨 父级「重新渲染!!现在我重新创建了我已有的函数!!」
👼 子级「妈妈!!把你的函数作为 props 给我!!」
👨 父级「好的,我把我的函数给你!」
👼 子级「好吧,现在我需要确认这个对象的内存地址是否与我之前获得的对象相同……嗯,地址不同,我也重新渲染了!!!」

为了防止这种不必要的重新渲染,应该使用 useCallback

文章来源:https://dev.to/kaziusan/history-of-stop-unnecessary-re-rendering-component-in-react--146f
PREV
用于 PWA 开发的高效堆栈
NEXT
关于 React 18 RC 你需要知道的一切