[react性能优化]--防止react-re-render: Why Suspense and how ?

react,性能,优化,防止,re,render,why,suspense,and,how · 浏览次数 : 17

小编点评

## React re-render 问题的解决方案和 freeze 在 React 内部实现原理 **问题:** 由于路由缓存不再适用,需要适配新的路由缓存方案。然而,react re-render 在这种情况下会困扰开发者,因为 React 需要在组件重新渲染之前重新渲染其子组件。 **解决方案:** 1. **useMemo 和 useEffect:**使用 `useMemo` 和 `useEffect` 来缓存子组件渲染的结果,并阻止每次 re-render 的重新渲染。 2. **React.memo:**使用 `React.memo` 高阶组件,确保子组件只在组件的 props 或状态发生变化时重新渲染。 3. **freeze:**使用 `freeze` 方法对 context 的值进行缓存,避免子组件重新渲染时对 context 进行更新。 4. **Suspense:**使用 `Suspense` 来缓存子组件渲染的结果,并在组件重新渲染之前触发回调函数来更新视图。 **freeze 在 React 内部实现原理:** * `useMemo` 和 `useEffect` 用于缓存子组件渲染的结果。 * `React.memo` 高阶组件确保子组件只在组件的 props 或状态发生变化时重新渲染。 * `freeze` 方法用于对 context 的值进行缓存。 **总结:** 为了解决 React re-render 问题,可以使用以下几种方法: * 使用 `useMemo` 和 `useEffect` 来缓存子组件渲染的结果。 * 使用 `React.memo` 高阶组件来阻止子组件重新渲染。 * 使用 `freeze` 方法对 context 的值进行缓存。 * 使用 `Suspense` 来缓存子组件渲染的结果,并在组件重新渲染之前触发回调函数来更新视图。

正文

近期内部项目基础项目依赖升级,之前使用的路由缓存不再适用,需要一个适配方案。而在此过程中react re-render算是困扰了笔者很久。后来通过多方资料查找使用了freeze解决了此问题。本文主要论述react re-render问题一般的解决方案和freeze在react内部的实现原理。react版本17.0.2

为什么会有re-render

首先re-render发生在某个react应用需要更新其状态的时候,这个状态一般分为三类

  1. 自身state发生变化
  2. 自身props发生变化
  3. 依赖的context发生变化

这三类更新一般都是正常的,是react应用完成其更新所必需要触发的,但是有部分re-render是非必需的,针对非必需的re-render是我们现在需要讨论的。

讨论之前需要多说一句的是, 对于一个合理的符合react理念编排的应用,其实re-render一般花费不了多少时间,防止re-render不能为了防止去防止

减少re-render的一般措施

  1. 父组件state变化之后,除了自身render之外,其所有子组件都会发生re-render

此处re-render的直接原因是,父组件在自己render的时候,会再一次调用:

React.createElement(Child, {props}, children)

之后返回的子节点上props发生了变化在begin work阶段无法走优化策略进而触发了re-render。

此处防止re-render的方法是用React.memo 或者使用useMemo包裹组件:

const MemoChild2 = useMemo(() => {
    return <Child2 />
  }, [])
 ...
 return <>{MemoChild2}</>
 ...

诸如此类因为父组件自身state变化而引起的re-render,还有些措施就是将state下移,即那个子组件需要这个state,就将这个state下移到该组件,避免这个子组件兄弟组件的更新

  1. 第二种是在A组件内声明B组件, 此种用法性能消耗更加吓人,根本原因是,A组件每次render函数运行之后,B都是一个新组件,对应的fiber节点上的type属性更新前跟更新后就不再指向同一组件,A组件的每一次render都会导致B组件的卸载跟挂载,根本不会存在复用:
function A() {
  const B = function() {
    useEffect(() => {
      console.log('B render')
      return () => {
        console.log('B destroy')
      }
    }, [])
    return <div>B</div>
  }
  return <>
    <div>A</div>
    <B />
  </>
}

此时若A re-render:

image.png
解决此re-render的方法就是: 将B组件移出A render函数之内:

const B = function() {
  console.log('B render')
  useEffect(() => {
    return () => {
      console.log('B destroy')
    }
  }, [])
  return <div>B</div>
}
function A() {
  console.log('A render')
  
  return <>
    <div>A</div>
    <B />
  </>
}

此时再re-render:

image.png
可以看到B没有再destroy,如果防止re-render可以参考1

  1. 组件依赖的context发生了变化

    如果组件依赖的context 发生了变化, 那么无论useMemo或者memo,都将无法起到作用。

如何防止context变化时组件的re-render

  1. 明确原则,此时的re-render是必须的,下边讨论的都是你的组件不需要re-render的时候,可以做的措施
  2. 首先你的context.provider 的value 不能在value值本身没有变化的时候而发生变化,否则子组件都会因为自身依赖的context变化而重新render,可以做的措施是useMemo等方法将value缓存起来
  3. 在2的基础上可以尝试将不同功能的context进行分割,即使用多个context
  4. 使用freeze,freeze内部其实就是suspense实现的,代码只有几行感兴趣的可以去github看看。

why and how

  1. Why?

因为已经找不到别的缓存策略来解决context发生变化时的re-render了, context变化时组件对应的updatelane为1, 会直接绕过beginwork阶段的优化策略。

  1. how?
  • Suspense 。
    一般搭配react.lazy 食用, 内部原理大体是这样的,首先 render阶段,其child会指向我们要加载的第一个组件,然后当直接child未加载成功时,beginwork阶段的react执行到child时会抛出一个错误,这个错误包含了我们的加载组件时写的那个promise,然后react在其then方法上会添加一个回调函数,用于更新Suspense. 随后,将下一个要遍历的fiber节点重置为Suspense,当begin work阶段再次执行到Suspense的时候,会在其child到sibling指向fallback,并将下一个要遍历的fiber节点置为fallback, 最后在组件加载成功时触发回调函数, 完成组件的加载。
  • 由以上的原理描述我们可以看到,Suspense在遇到抛出的异常时,是“不会管”自己的child节点的,而只是说会在child节点的sibiling上携带一个fallback节点, 那么基于此我们的child节点是可以保留之前的状态的,最重要的是,他会将下一个fiber节点置为fallback节点,因此也就绕过了我们child节点在后续的可能在begin work阶段触发re-render的机制
  • 那么freeze呢,就是根据某个属性,通过周期性的抛出异常,来避免了re-render

Suspense原理参考(https://juejin.cn/post/7145450651383201822)

与[react性能优化]--防止react-re-render: Why Suspense and how ?相似的内容:

[react性能优化]--防止react-re-render: Why Suspense and how ?

近期内部项目基础项目依赖升级,之前使用的路由缓存不再适用,需要一个适配方案。而在此过程中react re-render算是困扰了笔者很久。后来通过多方资料查找使用了freeze解决了此问题。本文主要论述react re-render问题一般的解决方案和freeze在react内部的实现原理。reac

React组件设计之性能优化篇

>我们是[袋鼠云数栈 UED 团队](http://ued.dtstack.cn/),致力于打造优秀的一站式数据中台产品。我们始终保持工匠精神,探索前端道路,为社区积累并传播经验价值。 >本文作者:空山 # 前言 > 由于笔者最近在开发中遇到了一个重复渲染导致子组件状态值丢失的问题,因此关于性能优化

Vue渲染函数与JSX指南

title: Vue渲染函数与JSX指南 date: 2024/6/3 下午6:43:53 updated: 2024/6/3 下午6:43:53 categories: 前端开发 tags: Vue渲染 JSX基础 性能优化 组件对比 React JSX 大项目 测试策略 第1章:Vue.js入门

React请求机制优化思路

说起数据加载的机制,有一个绕不开的话题就是前端性能,很多电商门户的首页其实都会做一些垂直的定制优化,比如让请求在页面最早加载,或者在前一个页面就进行预加载等等。

Java与React轻松导出Excel/PDF数据

前言 在B/S架构中,服务端导出是一种高效的方式。它将导出的逻辑放在服务端,前端仅需发起请求即可。通过在服务端完成导出后,前端再下载文件完成整个导出过程。服务端导出具有许多优点,如数据安全、适用于大规模数据场景以及不受前端性能影响等。 本文将使用前端框架React和服务端框架Spring Boot搭

想让你的工作轻松高效吗?揭秘Java + React导出Excel/PDF的绝妙技巧!

**前言** 在B/S架构中,服务端导出是一种高效的方式。它将导出的逻辑放在服务端,前端仅需发起请求即可。通过在服务端完成导出后,前端再下载文件完成整个导出过程。服务端导出具有许多优点,如数据安全、适用于大规模数据场景以及不受前端性能影响等。 本文将使用前端框架React和服务端框架Spring B

日常工作中需要避免的9个React坏习惯

前言 React是前端开发领域中最受欢迎的JavaScript库之一,但有时候在编写React应用程序时,可能陷入一些不佳的习惯和错误做法。这些不佳的习惯可能导致性能下降、代码难以维护,以及其他问题。在本文中,我们将探讨日常工作中应该避免的9个坏React习惯,并提供相关示例代码来说明这些问题以及如

两张图带你全面了解React状态管理库:zustand和jotai

zustand 和 jotai 是当下比较流行的react状态管理库。其都有着轻量、方便使用,和react hooks能够很好的搭配,并且性能方面,对比React自身提供的context要好得多,因此被很多开发小伙伴所喜爱。 更有意思的是,这两个库的作者是同一个人,同时他还开源了另外一个状态库 va

vue和react的相同点和不同点

Vue和React作为现代前端开发中流行的两个JavaScript框架,它们有诸多相似之处,同时也存在一些关键性的不同。以下是Vue和React的一些主要相同点和不同点: 相同点: 虚拟DOM:Vue和React都使用虚拟DOM(Virtual DOM)来提高性能,减少直接操作真实DOM的频率,从而

在HTML中引入React和JSX

## 前言 Vue 可以非常方便地与 Pure HTML 结合,代替 jQuery 的功能,有一次遇到类似的场景时,我就想 React 能不能也以这种方式接入 HTML 网页,从而提高开发效率。 结果当然是可以的,只不过在 HTML 里直接 JSX 似乎会降低一些性能… 凑合用吧 ## 引入依赖 要