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

显示不佳请移步(掘金查看)[https://juejin.cn/post/7186241311488344121]

为什么会有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:


解决此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:


可以看到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阶段的优化策略。

  2. 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-re-render: Why Suspense and how ?的更多相关文章

  1. react之——render prop

    在react “从上至下的数据流原则” 背景下,常规的消息传递机制就是通过prop属性,把父级数据传递给子级,这样一种数据流通模式决定了——数据的接收方子组件要被”硬植入“进数据的数据的给予方父组件, ...

  2. react解析: render的FiberRoot(三)

    react解析: render的FiberRoot(三) 感谢 yck: 剖析 React 源码解析,本篇文章是在读完他的文章的基础上,将他的文章进行拆解和加工,加入我自己的一下理解和例子,便于大家理 ...

  3. React components render order All In One

    React components render order All In One components render order / components lifecycle DOM tree ren ...

  4. React中render Props模式

    React组件复用 React组件复用的方式有两种: 1.render Props模式 2.高阶组件HOC 上面说的这两种方式并不是新的APi. 而是利用Raect自身的编码特点,演化而来的固定编码写 ...

  5. React在Render中使用bind可能导致的问题

    因为bind在render的时候会重现生成,这样会导致props每次都不同, puremixin的插件也会失效. 所以需要将bind的结果缓存下来,或者直接在constructor里做这个事情 con ...

  6. React 之 render props 的理解

    1.基本概念 在调用组件时,引入一个函数类型的 prop,这个 prop定义了组件的渲染方式. 2.回调渲染 回顾组件通信的几种方式 父-> 子 props 子-> 父 回调.消息通道 任 ...

  7. [React Intl] Render Content Based on a Number using react-intl FormattedMessage (plural)

    Using the react-intl FormattedMessage component, we’ll learn how to render content conditionally in ...

  8. [React Intl] Render Content with Markup Using react-intl FormattedHTMLMessage

    In this lesson, we’ll use the react-intl FormattedHTMLMessage component to display text with dynamic ...

  9. [React Intl] Render Content with Placeholders using react-intl FormattedMessage

    Learn how to use react-intl to set dynamic values into your language messages. We’ll also learn how ...

  10. webpack打包经验——处理打包文件体积过大的问题

    前言 最近对一个比较老的公司项目做了一次优化,处理的主要是webpack打包文件体积过大的问题. 这里就写一下对于webpack打包优化的一些经验. 主要分为以下几个方面: 去掉开发环境下的配置 Ex ...

随机推荐

  1. Ubuntu 系统下搭建 SRS 流媒体服务器

    一.克隆项目 git clone https://github.com/ossrs/srs 二.执行编译 cd srs/trunk sudo ./configure && make 三 ...

  2. RTFormer: Efficient Design for Real-Time Semantic Segmentation with Transformer概述

    0.前言 相关资料: arxiv github 论文解读 论文基本信息: 发表时间:NeurlPS2022(2022.10.13) 1.摘要 最近,基于Transformer的网络在语义分割方面取得了 ...

  3. javaSE学习一

    java基础 java是一种强类型语言:要求变量的使用要严格符合规定,所有变量都必须先定义后才能使用. java的数据类型有两大类:基本数据类型和引用类型(类.接口.数组) 八大基本数据类型: 1.整 ...

  4. redis-centOS系统的环境安装

    近期要简单汇总一下Redis常用的方法,依赖环境配置,在网上看到一篇比较好的Redis环境搭建博客,分享给大家.因新版本Redis已不支持windowns,环境安装以centOS系统为主. 参考博客地 ...

  5. 开启MySQL数据库远程连接

    为了使其余用户/计算机能访问SQL数据库,需对SQL Server进行以下配置.有以下两种方法: 方法一:bat命令修改. 新建.txt文件,添加以下内容,保存后再修改为.bat,双击.bat文件. ...

  6. liunx常用命令必备,持续更新

    inux中的命令的确是非常多,但是只需要掌握我们最常用的命令足够完成我们的工作了. 1.切换超级用户与普通用户 默认登录的是普通用户权限显示$符从普通用户切换超级用户权限:sudo su输入密码 从超 ...

  7. <input>输入框,限制输入的为正整数

    <input id="eventId" col="EventId" type="text" class="form-cont ...

  8. MySQL数据库SQL语法常规操作

    必备sql和表关系及授权 graph LR 执行1[必备sql和授权] 执行2[SQL强化和实践] 执行3[索引和函数以及存储过程] 执行4[Python操作mysql和应用] 执行5[常见SQL语句 ...

  9. 用 yaml 写测试用例

    采用 excel 的方式写测试用例,格式比较死板,比较容易出错,采用 yaml / yml 格式来写用例. 首先安装 pyyaml:pip install pyyaml 建立一个 login.yaml ...

  10. 关于Java中数组的简单使用

    关于java中数组的简单使用--继java环境配置后的第二篇学习笔记 近期在学习Java的过程中学到了数组的部分,至于为什么我会到数组才来写这个,主要是数组这一章节的内容感觉还是与之前学的C里面的数组 ...