react组件多次渲染问题

在React应用中,即使数据未变化,组件也可能因父组件渲染而重复渲染。通过React.memo可以优化这种情况,但需注意props的变更和内部组件的影响。使用memo时,确保props对象最小化,避免函数在组件内部定义,以减少不必要的渲染。

问题背景

在数据没有发生变化的情况下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>
}

测试结论:子组件即使不接收任何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>
            {/* <ChildComponent /> */}
            <MemoChild />
        </div>
    )
}

function ChildComponent() {
    console.log("ChildComponent重新渲染了")
    return <div>Hello from ChildComponent</div>
}

const MemoChild = React.memo(ChildComponent)

即使使用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>
            {/* <ChildComponent /> */}
            <MemoChild />
        </div>
    )
}

function ChildComponent() {
    console.log("ChildComponent重新渲染了")
    return <div>Hello from ChildComponent</div>
}

const MemoChild = React.memo(ChildComponent)

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

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

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

问题结论

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

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

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

  • 重复的渲染可以使用memo缓存
  • 父组件传递的重复的props参数可以使用useMemo
  • 每次传递的props应当是最小的props,不然无关的props变更会影响组件缓存失效
  • 组件及函数应该尽量以纯函数形式写在组件之外,避免组件重渲染时重新创建函数带来的影响
### 反应组件间数据渲染延迟的原因 反应框架中的数据渲染延迟主要源于其工作原理以及特定场景下的设计局限。当组件的状态或属性发生变化时,反应并不会立刻重新渲染整个应用树,而是通过一种称为“协调”的过程来决定哪些部分需要更新[^5]。这一机制虽然提高了性能,但在某些情况下可能导致视觉上的延迟感。 具体来说,在函数组件中使用 `useEffect` 钩子处理副作用(如从后端获取数据),可能会因为异步特性和状态管理不当而引发延迟问题[^3]。此外,早期版本的反应采用同步渲染模式,这意味着长时间运行的任务会阻塞主线程,进一步加剧了用户体验中的卡顿现象[^5]。 ### 解决方案概述 针对上述提到的数据渲染延迟问题,有多种策略可以帮助开发者优化用户体验: #### 使用 `useEffect` 正确处理数据请求 确保在 `useEffect` 中合理设置依赖项列表,以便仅在必要条件下触发效果回调函数执行。这样可以防止不必要的多次调用API接口或者重复计算逻辑[^3]。 ```javascript import { useState, useEffect } from 'react'; function DataFetchingComponent() { const [data, setData] = useState(null); useEffect(() => { fetch('/api/data') .then(response => response.json()) .then(setData); }, []); // Empty dependency array ensures this runs once after initial render. return data ? <div>{JSON.stringify(data)}</div> : <p>Loading...</p>; } ``` #### 利用 Suspense 提升加载体验 对于涉及多个资源加载的情况,可以考虑引入 `<Suspense>` 组件配合懒加载技术,使得页面其他区域能够正常显示而不必等待所有内容都准备完毕后再统一展示[^4]。 #### 异步更新与并发模式的支持 自 React 18 起支持新的并发特性,允许更灵活地控制何时何地进行渲染操作。例如,通过调度器 API (`startTransition`) 来标记那些不紧急但又希望尽快完成的操作为过渡态,从而让更重要的交互优先得到反馈[^5]。 ```javascript import { startTransition } from 'react-dom/client'; // Inside your component... const handleClick = () => { startTransition(() => { setNewStateValue(newValue); }); }; ``` ### 结论 综上所述,要有效应对React组件间的数据显示滞后状况,需综合考量项目实际需求选取合适的技术手段加以改进。无论是调整现有代码结构还是升级至最新版库文件,均能不同程度缓解此类难题带来的困扰。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值