什么是 re-render(重新渲染)?哪些是必要的 re-render?哪些是非必要的 re-render?如果你对这些问题还不是很明白,那么可以在这篇文章中找到答案。本文是一篇比较详尽的 React re-render 指南,会着重介绍和解答以下问题:什么是 re-render、哪些是必要或非必要 的 re-render、什么条件能够触发 React 组件 re-render。除此之外,还会介绍一些避免 re-render 的重要开发模式,以及引起不必要 re-render 的反模式。并且针对每种模式和反模式都提供了示例图。
每当提及 React 的性能时,我们需要特别关注下面两个渲染阶段:
当页面更新数据的时候,React 组件就会发生 re-render,比如用户与页面之间产生交互、异步请求数据或者订阅的外部数据更新等这些场景都会导致 re-render。那些没有任何异步数据更新的非交互式应用程序永远不会发生 re-render,因此这种应用场景不需要关心 re-render 的性能优化。
哪些是必要或非必要的 re-render
必要的 re-render:组件发生重新渲染的原因是数据发生了变化,组件要把最新的数据渲染到页面上。例如,用户在输入框中输入文字,组件在每次按键时通过状态管理完成更新和渲染,对于这个组件来说这就是必要的 re-render。
不必要的 re-render:由于错误的实现方式,某个组件的 re-render 导致了整个页面全部重新渲染,这就是不必要的 re-render。比如,用户在输入框中输入文字,并且在每次按键时整个页面都进行了渲染,对于整个页面来说这就是非必要的 re-render。
非必要的 re-render 本身不存在问题:React 非常快速,通常能够在用户还未注意到的情况下处理它们。然而,如果 re-render 过于频繁或在非常重的组件上进行时,可能会让用户感觉到 “卡顿”,在交互过程中会出现明显延迟,甚至页面完全没有响应。
组件发生重新渲染有四个原因:状态更改、父级(或子级)重新渲染、context 变化以及 hooks 变化。这里有一个很大的误区:当组件的 props 改变时,组件会重新渲染。就其本身而言,这并不是真的(见本文后面的介绍)。
当组件的状态发生变化时,它将重新渲染自身。通常,它发生在回调或 useEffect 中。状态变化是所有重新渲染的根因。
如果组件的父组件重新渲染,则组件将重新渲染自身。反过来看也是对的:当组件重新渲染时,它也会重新渲染其所有子组件。但是,子组件的重新渲染不会触发父级的 re-render。
当 Context Provider 中的值发生变化时,使用该 Context 的所有组件都要 re-render,即使它们并没有使用发生变化的那部分数据。这些 re-render 并不能直接通过 memoize 来避免掉,但是可以用一些变通的方法来避免(参见第7部分:防止由 Context 引起的重新渲染)。
hooks 中发生的一切都“属于”使用它的组件。因此 Context 和 State 的更新规则同样也适用于这里:
hooks 可以嵌套使用,其中每个 hooks 都 “属于” 使用它的组件,相同的规则适用于其中任何一个 hooks。
re-render 误区:**props 变化**
说到未被 memo 包裹的组件 re-render 时,组件的 props 是否发生变化并不重要。组件的 props 即便是发生了改变,也是由父组件来更新它们。也就是说,父组件的重新渲染触发了子组件的重新渲染,与子组件的 props 是否变化无关。只有那些使用了 React.memo 和 useMemo 的组件,props 的变化才会触发组件的重新渲染。
反模式:在 render 函数中创建组件
在组件的渲染函数中创建一个组件是一种反模式,很有可能会引起性能问题。在每次重新渲染时,React 都会重新装载这个组件(即销毁它并从头开始重新创建),这比正常的重新渲染要慢得多。除此之外,还会导致以下问题:
组件复合避免 re-render:state 下移到子组件中
这个模式非常有用,特别是对逻辑比较复杂的组件做状态管理时,并且这些状态仅仅用在了渲染树的一小部分上。一个典型的例子就是,在页面重要且复杂的组件中,实现单击按钮打开/关闭对话框。在这种情况下,控制对话框开关的状态可以封装在较小的组件中。这样,整个大组件就不会因为这些状态的更改而发生重新渲染。
组件复合避免 re-render:children 作为 props
这个模式与状态下移比较类似,也是将状态变化封装在较小的组件中。不同之处在于,这里的状态是作用于包裹复杂组件的父组件上,因此无法通过状态下移的方式作用于较小的组件。一个典型的例子是绑定到组件根元素上 onScroll 或 onMouseMove 回调。在这种场景下,可以将状态管理和使用该状态的组件提取到较小的组件中,并且可以将较复杂的组件作为 children props 传递给它。从较小的组件角度来看,children 只是 props,因此它们不会受到状态更改的影响,因此不会重新渲染。
组件复合避免 re-render:组件作为 props
与前面的模式基本相同,将状态封装在一个较小的组件中,而较重的组件作为 props 传递给它。props 不受 state 变化的影响,因此该较重的组件不会被重新渲染。这个模式适用于那些状态独立的复杂组件,并且不能抽离成一个 children 属性的场景。
用 React.memo 包装的组件会阻止重新渲染,除非这个组件的 props 发生了变化。这对于不依赖于重新渲染的组件,是非常有用的。
React.memo: 带有 props 的组件
对于非基础数据类型的 props 都要用 React.memo 包装成为 memoize 值。
React.memo: 作为 props 或 children 的组件
React.memo 可用于作为 props 或 children 的元素。仅对父组件进行包装是不起作用的:children 和 props 作为对象类型会在每次父组件渲染时都会变化,进而引起子组件重新渲染。
反模式:无用的 useMemo/useCallback props
将子组件的 props 包装成 memoize 值,是不能避免该子组件重新渲染的。只要父组件重新渲染,那么子组件就会被重新渲染,与 props 没有关系。
有用的 useMemo/useCallback
如果子组件被 React.memo 包装,那么它的所有非基础数据类型的 props 也要做 memoize 处理。
如果组件使用的 hooks(比如 useEffect、useMemo、useCallback)依赖了非基础数据类型的值,那么要做 memoize 处理。
使用 useMemo 处理昂贵的计算
useMemo 的其中一个使用场景是避免每次重新渲染时进行昂贵的计算。useMemo 也是有其成本的(消耗一点内存并使初始渲染稍微慢一点),因此不应将其用于每次计算。在 React 中,挂载和更新组件在大多数情况下都是最昂贵的计算(除非您实际计算的是基础类型,而您无论如何都不应该在前端这样做)。
因此,useMemo 的典型场景是对 React 元素做 memoize 处理。与组件更新相比,数组的排序或过滤等“纯” JavaScript 操作的成本基本可以忽略不计。
除了上述的 re-render 规则和模式之外,key
的值会影响列表的渲染性能。仅仅设置 key
值并不会提高列表的性能。为了避免列表元素的重新渲染,还要用 React.memo 包装它们,并且还要遵循一些最佳实践。
key
的值应为字符串,列表中的元素在每次重新渲染时,这个字符串要保持一致。通常使用数据的 id 或 数组的索引作为 key 值。如果列表是静态的,比如元素不会有增加、删除、插入、排序等操作,使用数组的 index 作为 key 是没有问题的。但是在动态列表中使用数组的索引会导致一些问题:
反模式:使用随机数作为 key 值
随机数不要作为列表的 key 值,因为随机数会导致列表元素在每次重新渲染时重新挂载元素:
将 Provider 的值做 memoize 处理
如果 Context Provider 没有在页面的根节点上,那么祖先节点的变化也会导致它被重新渲染,所以它的值也应该被 memoize。
避免 Context 引起 re-render:对数据和 API 做拆分
如果在 Context 中存在数据和 API 的组合(getter 和 setter),则可以将它们拆分为同一组件下的不同 Providers。这样,仅使用 API 的组件在数据变化时不会重新渲染。
避免 Context 引起 re-render:对数据拆分
如果 Context 管理几个独立的数据块,则可以将它们拆分为同一 Provider 下的较小 Provider。这样,只有变化的数据块的使用者才会重新渲染。
避免 Context 引起 re-render:Context selector
对于那些部分使用了 Context 值的组件,即使使用了 useMemo hooks 也无法避免组件的重新渲染。但是,我们可以使用高阶组件和 React.memo 可以伪造出 Context selector。
本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/LAylku9OzNsRLzOuKerGVA
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。