React 重新渲染深度解析:为何 children 组件会被重复渲染及优化策略


react 重新渲染深度解析:为何 children 组件会被重复渲染及优化策略

本文深入探讨了 React 组件在父组件状态更新时,即使通过 children prop 传递,子组件仍可能被重复渲染的常见问题。核心原因在于父组件每次渲染时,若子组件在 JSX 中被内联声明,React 会创建新的子组件实例。文章通过具体代码示例,详细解释了这一机制,并提供了将状态管理下移以稳定 children prop 的优化方案,旨在帮助开发者更好地理解和优化 React 应用的渲染性能。

理解 React 组件的重新渲染机制

在 React 应用开发中,组件的重新渲染(re-render)是一个核心概念,但其行为有时会超出开发者的预期。一个常见的误解是,如果一个父组件的 children prop 逻辑上没有改变,那么传递给它的子组件就不会重新渲染。然而,当父组件自身因状态更新而重新渲染时,如果子组件是在父组件的 JSX 渲染逻辑中内联声明的,即使其内容看似不变,React 也会将其视为一个新的组件实例,从而触发子组件的重新渲染。

这背后的机制是:每当一个组件函数执行时(即发生渲染),如果其 JSX 中包含对另一个组件的引用,React 会为该引用创建一个新的 React 元素(React Element)。即使这个新的 React 元素在类型和 props 上与上一次渲染的元素完全相同,但由于它是一个“新”的对象实例,React 在协调(reconciliation)过程中会认为这个子组件可能需要更新,并会访问其子树进行比对,最终导致子组件函数被再次调用,即重新渲染。

问题示例:children 组件的意外重新渲染

考虑以下 React 应用结构,其中 App 组件包含一个定时器,每 100 毫秒更新一次自身状态,进而导致 App 组件重新渲染。App 将 Child 组件作为 Parent 组件的 children prop 传递。

import { useState, useEffect } from 'react';

// Child 组件,每次渲染都会在控制台输出 'rendered'
const Child = () => {
  console.log('Child rendered');
  return (
    <p>这是一个子组件内容。</p>
                    <div class="aritcle_card">
                        <a class="aritcle_card_img" href="/ai/1202">
                            <img src="https://img.php.cn/upload/ai_manual/001/431/639/68b7a1824cc48323.png" alt="CA.LA">
                        </a>
                        <div class="aritcle_card_info">
                            <a href="/ai/1202">CA.LA</a>
                            <p>第一款时尚产品在线设计平台,服装设计系统</p>
                            <div class="">
                                <img src="/static/images/card_xiazai.png" alt="CA.LA">
                                <span>86</span>
                            </div>
                        </div>
                        <a href="/ai/1202" class="aritcle_card_btn">
                            <span>查看详情</span>
                            <img src="/static/images/cardxiayige-3.png" alt="CA.LA">
                        </a>
                    </div>
                
  );
}

// Parent 组件,接收并渲染 children prop
const Parent = ({ children }) => {
  return (
    <div id='parent'>
      {children}
    </div>
  );
}

// App 组件,包含一个定时器更新自身状态
const App = () => {
  const [now, setNow] = useState();

  // 启动一个定时器,每100ms更新 'now' 状态
  useEffect(() => {
    const interval = setInterval(() =>
      setNow(Date.now()), 100);
    return () => clearInterval(interval); // 清理定时器
  }, []);

  return (
    <div className="App">
      <Parent>
        {/* Child 组件在这里被内联声明 */}
        <Child />
      </Parent>
    </div>
  );
}

export default App;

运行上述代码,你会发现控制台每 100 毫秒都会输出 Child rendered。尽管 Child 组件没有任何自身状态或接收任何 props,并且 Parent 组件也只是简单地渲染其 children prop,Child 依然在 App 组件状态更新时被重复渲染。

原因分析:

当 App 组件的状态 now 更新时,App 组件会重新执行其渲染函数。在每次执行时,App 组件的 JSX 表达式 都会被重新评估。这意味着,每次 App 渲染时,都会创建一个新的 React 元素实例。

尽管这个新的 元素与上一次渲染的 元素在类型和结构上是相同的,但对 React 而言,它是一个全新的 J*aScript 对象引用。因此,当 App 将这个新的 元素作为 Parent 的 children prop 传递时,React 会认为 Parent 组件的 children prop 已经改变(因为它是一个新的对象引用),从而触发 Parent 和其 children(即 Child 组件)的重新渲染。

优化方案:稳定化 children Prop

为了避免 Child 组件在 App 组件状态更新时进行不必要的重新渲染,我们需要确保传递给 Parent 组件的 children prop 在 App 重新渲染时保持引用稳定。最直接有效的方法是将导致重新渲染的状态或副作用下移到组件树中更低层级的组件,使其不会影响到不相关的上层组件或兄弟组件。

在我们的例子中,App 组件中的定时器状态更新是导致 Child 重新渲染的根本原因。如果我们将这个定时器逻辑移动到 Parent 组件内部,那么 App 组件将不再因定时器而重新渲染,从而稳定了传递给 Parent 的 children prop。

import { useState, useEffect } from 'react';

const Child = () => {
  console.log('Child rendered');
  return (
    <p>这是一个子组件内容。</p>
  );
}

// Parent 组件,现在包含了定时器状态
const Parent = ({ children }) => {
  const [now, setNow] = useState(); // 状态移至 Parent

  // 启动一个定时器,每100ms更新 'now' 状态
  useEffect(() => {
    const interval = setInterval(() =>
      setNow(Date.now()), 100);
    return () => clearInterval(interval);
  }, []);

  return (
    <div id='parent'>
      {children}
    </div>
  );
}

// App 组件现在不包含定时器逻辑
const App = () => {
  return (
    <div className="App">
      <Parent>
        {/* Child 组件仍然在这里被内联声明 */}
        <Child />
      </Parent>
    </div>
  );
}

export default App;

通过将 useState 和 useEffect 钩子从 App 组件移动到 Parent 组件,App 组件在首次渲染后,其内部将不再有状态更新导致自身重新渲染。这意味着 这部分 JSX 表达式只会在 App 组件挂载时执行一次。因此,传递给 Parent 的 React 元素引用将保持稳定。

现在,Parent 组件会每 100 毫秒更新其内部的 now 状态并重新渲染。然而,由于 App 组件不再重新渲染,它传递给 Parent 的 children prop(即 元素)的引用始终是第一次创建的那个。当 Parent 重新渲染时,React 发现其 children prop 的引用没有改变,因此不会访问 Child 组件的子树,Child 组件也就不会重新渲染。此时,控制台将只输出一次 Child rendered。

注意事项与最佳实践

  1. 状态下移原则: 尽可能将状态和副作用放置在组件树中需要它们的最底层组件。这可以有效限制重新渲染的范围,避免不必要的性能开销。

  2. React.memo 的作用: 对于功能组件,可以使用 React.memo 来进行性能优化。React.memo 会对组件的 props 进行浅比较,如果 props 没有改变,则跳过组件的重新渲染。在上述示例中,即使 App 仍然包含定时器,如果 Child 组件被 React.memo 包裹,并且它不接收任何 props,它将不会重新渲染。但请注意,React.memo 仅在 props 引用稳定时才有效。如果像原始问题中那样,每次父组件渲染都创建一个新的 Child 元素,即使 Child 被 memo 包裹,它仍然会接收到一个“新”的 children prop 引用,从而导致重新渲染。

    const MemoizedChild = React.memo(() => {
      console.log('MemoizedChild rendered');
      return <p>这是一个被 memoized 的子组件内容。</p>;
    });
    
    // ... 在 App 中使用 <MemoizedChild />

    然而,在我们的原始问题场景中,Child 是作为 Parent 的 children prop 传递的。如果 Parent 被 React.memo 包裹,而 App 每次都传递一个新的 元素给 Parent 的 children prop,那么 Parent 的 children prop 引用仍然会改变,Parent 依然会重新渲染,并进而渲染其 children。因此,React.memo 主要用于优化组件自身因父组件 props 变化而导致的重新渲染,而不是解决父组件内联创建子组件实例的问题。

  3. 理解 JSX 的本质: JSX 语法糖最终会被编译成 React.createElement() 调用。每次父组件渲染时,JSX 中的组件标签都会被转换为新的 React.createElement() 调用,生成新的 React 元素对象。理解这一点有助于避免关于组件重新渲染的误解。

  4. 组件组合的权衡: “提升内容” (lifting content up) 是一种强大的组合模式,可以将子组件的渲染逻辑与父组件的业务逻辑解耦。但在使用时,需要注意其对渲染性能的影响,确保传递的 children prop 引用在不必要时是稳定的。

总结

React 的渲染机制是其高性能的基础,但如果不深入理解其工作原理,可能会遇到一些性能陷阱。当父组件重新渲染且在 JSX 中内联声明子组件时,React 会创建新的子组件实例,即使其逻辑内容不变,也可能导致不必要的重新渲染。通过将状态和副作用下移到更合适的组件层级,我们可以有效稳定传递给子组件的 children prop 引用,从而避免这些不必要的重新渲染,提升应用的性能和用户体验。

以上就是React 重新渲染深度解析:为何 children 组件会被重复渲染及优化策略的详细内容,更多请关注其它相关文章!


# javascript  # react  # 创建一个  # 是一个  # 使其  # 这是一个  # 子树  # red  # 组件渲染  # 常见问题  # 应用开发  # app  # js  # java  # 肇庆医疗网站建设推广  # seo推广的因素  # 揭阳seo按天收费  # 南京网站关键词优化报价  # 台商开发区行业网站推广  # 金华专业的seo网站推广价格  # 公众号营销软文推广方式  # 股票五大关键词排名查询  # seo优化佛山  # 德化县定制网站建设设计  # 移到  # 表单  # 它是  # 会在  # 在这里 


相关栏目: 【 Google疑问12 】 【 Facebook疑问10 】 【 优化推广96088 】 【 技术知识133117 】 【 IDC资讯59369 】 【 网络运营7196 】 【 IT资讯61894


相关推荐: 《兴业银行》注册登录方法  铁路12306怎么申请退票_铁路12306退票申请操作流程  搜狗浏览器如何查找页面中的文字 搜狗浏览器Ctrl+F页面搜索功能  Go反射进阶:访问内嵌结构体中的被遮蔽方法  J*aScript大数运算_BigInt使用指南  《腾讯相册管家》注销账号方法  Python实战:高效处理实时数据流中的最小/最大值  快手缓存清理方法  Golang如何操作指针参数_Go pointer参数传递规则  Retrofit根路径POST请求:@POST("/") 的应用与解析  热血江湖归来医师加点攻略  TikTok视频播放中断怎么办 TikTok播放异常修复方法  我居然低估了 DeepSeek,这次更新它做到了这些!  拷贝漫画2025网页版入口 拷贝漫画官网免费看全集  c++如何链接Boost库_c++准标准库的集成与使用  百度网盘如何设置上传限额  解决 Vue 3 组件未定义错误:理解 createApp 与根组件的正确使用  铁路12306官网入口 铁路12306中国铁路官网登录首页  Fedora怎么安装 Fedora Workstation安装步骤  Win10如何关闭开机锁屏界面_Windows10跳过锁屏直接登录设置  Animex动漫社社登录官网 Animex动漫社资源社入口直达  优化Google Charts Gauge:在数据库无数据时显示默认值  百度浏览器无法安装扩展程序_百度浏览器插件安装失败原因解析  @Team是什么?揭秘团队含义  Python测试中模块导入路径解析的最佳实践  mysql中如何分析索引使用情况_mysql索引使用分析方法  Sublime Text怎么关闭自动完成_Sublime禁用Auto Complete设置  如何在CSS中实现盒模型多列间距_grid-gap与padding结合  Dash应用中自定义HTML页面标题与网站图标(F*icon)的实用指南  使用AI在VS Code中将代码从一种语言翻译成另一种  Win10怎么设置快速启动 Win10开启快速启动设置方法  《火影忍者:木叶高手》快速升级攻略  汽水音乐车机版官网5.0 汽水音乐车机版5.0版本下载入口  iQOO手机信号差网络不稳定怎么办 信号问题原因排查与增强设置【攻略】  iPhone 14 Pro如何更改区域设置_iPhone 14 Pro地区语言修改教程  J*aScript文本高亮功能优化:解决多词匹配错误与精确分割策略  在VS Code中进行数据科学和机器学习开发  Go Template中优雅处理循环最后一项:自定义函数实践  解决Flex容器横向滚动内容截断与偏移问题  CodeIgniter 3 中基于 MySQL 数据高效生成动态图表教程  j*a中赋值运算符是什么?  cad怎么隐藏指定的图层_cad隐藏或冻结图层方法  歌词怎么展示在|直播|间视频号?有什么注意事项?  Highcharts雷达图轴线交点数值标注指南  J*aScript中高效处理用户输入:从Keyup事件到表单提交的优化实践  lol小红书怎么|直播|?lol小红书|直播|是什么意思?  第五人格PC版怎么避免被封号_第五人格PC版防封号注意事项  Cassandra中复合主键、二级索引与ORDER BY排序的限制与解决方案  韩剧圈正版官网入口_韩剧圈官方指定登录  抖音网页版官方链接 抖音网页版官网链接入口 

 2025-10-06

了解您产品搜索量及市场趋势,制定营销计划

同行竞争及网站分析保障您的广告效果

点击免费数据支持

提交您的需求,1小时内享受我们的专业解答。

运城市盐湖区信雨科技有限公司


运城市盐湖区信雨科技有限公司

运城市盐湖区信雨科技有限公司是一家深耕海外推广领域十年的专业服务商,作为谷歌推广与Facebook广告全球合作伙伴,聚焦外贸企业出海痛点,以数字化营销为核心,提供一站式海外营销解决方案。公司凭借十年行业沉淀与平台官方资源加持,打破传统外贸获客壁垒,助力企业高效开拓全球市场,成为中小企业出海的可靠合作伙伴。

 8156699

 13765294890

 8156699@qq.com

Notice

We and selected third parties use cookies or similar technologies for technical purposes and, with your consent, for other purposes as specified in the cookie policy.
You can consent to the use of such technologies by closing this notice, by interacting with any link or button outside of this notice or by continuing to browse otherwise.