• react组件多次渲染问题


    问题背景

    在数据没有发生变化的情况下React组件会进行数次重复渲染,绘制出来完全相同的两个图

    排查思路

    寻找子组件重渲染原因实验

    测试一:在子组件的props未发生任何变更的情况下是否会发生重新渲染

    import React, { useState } from "react"
    import { Select } from "antd"
    
    const { Option } = Select
    
    export function Chart() {
        console.log("父组件重新渲染了")
        const [selectedOption, setSelectedOption] = useState("option1")
    
        function handleSelectChange(value) {
            setSelectedOption(value)
        }
    
        return (
            <div>
                <Select defaultValue={selectedOption} onChange={handleSelectChange}>
                    <Option value="option1">Option 1</Option>
                    <Option value="option2">Option 2</Option>
                    <Option value="option3">Option 3</Option>
                </Select>
                <ChildComponent />
            </div>
        )
    }
    
    function ChildComponent() {
        console.log("ChildComponent重新渲染了")
        return <div>Hello from ChildComponent</div>
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    测试结论:子组件即使不接收任何props当父组件重新渲染时子组件也会重新渲染

    解决方案

    使用React.memo,关于memo介绍如下:

    包装一个组件memo以获得该组件的记忆版本。只要组件的 props 没有改变,当它的父组件重新渲染时,组件的这个记忆版本通常不会被重新渲染。
    但 React 可能仍然会重新渲染它:memoization 是一种性能优化,而不是保证。

    import React, { useState } from "react"
    import { Select } from "antd"
    
    const { Option } = Select
    
    export function Chart() {
        console.log("父组件重新渲染了")
        const [selectedOption, setSelectedOption] = useState("option1")
    
        function handleSelectChange(value) {
            setSelectedOption(value)
        }
    
        return (
            <div>
                <Select defaultValue={selectedOption} onChange={handleSelectChange}>
                    <Option value="option1">Option 1</Option>
                    <Option value="option2">Option 2</Option>
                    <Option value="option3">Option 3</Option>
                </Select>
                {/*  */}
                <MemoChild />
            </div>
        )
    }
    
    function ChildComponent() {
        console.log("ChildComponent重新渲染了")
        return <div>Hello from ChildComponent</div>
    }
    
    const MemoChild = React.memo(ChildComponent)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    即使使用memo,但其仍然会在其接受的props发生变更时及其所在的上下文发生变更时重新渲染。

    因此为了更好的使用memo,应当尽量减少props的变化。具体方案如下:

    • 当props为引用值时应当使用useMemo返回相同引用值
    • 使得 props 中接受最少的必要信息
    • 当props包含函数时,要么在组件外部声明它以使其永远不会更改,要么useCallback在重新渲染之间缓存其定义。

    例如:当props为对象时,应当减少props对象的变化

    测试内组件对memo的影响

    接下来进行另外的测试,将子组件放在Adapt中称为内部组件

    可以发现,此时子组件的缓存失效了,尽管子组件没有props的变化

    但每次父组件重新渲染子组件仍然会重新渲染

    import React, { useState } from "react"
    import { Select } from "antd"
    
    const { Option } = Select
    
    export function Adapt() {
        console.log("父组件重新渲染了")
        const [selectedOption, setSelectedOption] = useState("option1")
    
        function handleSelectChange(value) {
            setSelectedOption(value)
        }
    
        return (
            <div>
                <Select defaultValue={selectedOption} onChange={handleSelectChange}>
                    <Option value="option1">Option 1</Option>
                    <Option value="option2">Option 2</Option>
                    <Option value="option3">Option 3</Option>
                </Select>
                {/*  */}
                <MemoChild />
            </div>
        )
    }
    
    function ChildComponent() {
        console.log("ChildComponent重新渲染了")
        return <div>Hello from ChildComponent</div>
    }
    
    const MemoChild = React.memo(ChildComponent)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    React.memo 失效的原因是因为将 ChildComponentMemoChild 定义在了 Adapt 组件的内部。这意味着每当 Adapt 组件重新渲染时,ChildComponentMemoChild 都会重新定义,导致了缓存失效。

    测试props变更对memo的影响

    改写测试用例,测试当父组件的props变更时,子组件是否重新渲染

    import React, { useState } from "react"
    import { Select } from "antd"
    
    const { Option } = Select
    
    export function Adapt() {
        console.log("Adapt重新渲染了")
    
        const [filterList, setFilterList] = useState(null)
    
        function onRefresh(value) {
            setFilterList(value)
        }
    
        return (
            <div>
                <Chart {...{ onRefresh, filterList }} />
            </div>
        )
    }
    
    function Chart({ filterList, onRefresh }) {
        console.log("Chart重新渲染了")
    
        return (
            <div>
                <Select value={filterList} onChange={onRefresh}>
                    <Option value="option1">Option 1</Option>
                    <Option value="option2">Option 2</Option>
                    <Option value="option3">Option 3</Option>
                </Select>
                <MemoChild />
            </div>
        )
    }
    
    function ChildComponent() {
        console.log("ChildComponent重新渲染了")
        return <div>hello</div>
    }
    
    const MemoChild = React.memo(ChildComponent)
    
    export default Adapt
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    子组件仍然可以进行正常的缓存,可得知当子组件使用memo

    无论是父组件的state或者props状态变更,只要子组件的props和上下文未变化都不会影响到子组件

    问题结论

    导致组件重复渲染的可能如下:

    • 当子组件未使用memo时,子组件会随着父组件一起重新渲染,因此即使子组件的props未变化仍会多次渲染子组件
    • 当子组件使用了memo,但仍然重新渲染
      • 子组件接受的props或者context变更了
      • 子组件的上下文及props都没变更,但子组件时内部组件所以每次都是重新创建的

    因此为了优化子组件的多次渲染应当注意:

    • 重复的渲染可以使用memo缓存
    • 父组件传递的重复的props参数可以使用useMemo
    • 每次传递的props应当是最小的props,不然无关的props变更会影响组件缓存失效
    • 组件及函数应该尽量以纯函数形式写在组件之外,避免组件重渲染时重新创建函数带来的影响
  • 相关阅读:
    接口请求返回状态码
    华为发布鸿蒙开发套件 全面加速推进鸿蒙生态
    vue+echarts项目一:前端准备工作
    Linux CentOS7 vim临时文件
    GeoSOS-FLUS未来土地利用变化情景模拟模型
    自然之州阿肯色及温泉国家公园
    javascript中的new原理及实现
    Vitis导入自制IP导致无法构建Platform
    Codeforces刷题记录(tag: combinatorics 873 ~ 1073)
    基于嵌入式的密码访问的门锁系统
  • 原文地址:https://blog.csdn.net/riddle1981/article/details/131501148