React 理解 re-render 的作用、概念,并提供详细的例子解释

news2024/9/24 15:12:08

一、什么是 re-render

在 React 中 re-render(重新渲染) 是经常发生的行为,主要确保视图要时刻保持最新的数据来呈现。

但每次发生 re-render 也是有代价的,比如数据状态、focus 焦点、表单数据、都得重置,
遇到代码复杂一点,还给性能带来不必要的消耗。

React 的 re-render 是一个很核心的概念,我们必须得掌握它,否则出现问题将无从下手。

下面我将会介绍触发 re-render 的场景,以及如何避免 re-render

二、什么场景会触发 re-render

前言:re-render 只会影响自身和 children,如果 children 还有 children 则继续影响,以此类推。
理解这点很重要,它将会作为后续如何避免 re-render 的解题思路。

提示:此 children 非 props.children

2.1 parent

parent 组件发生 re-render 时,自身与 children(子组件) 都会受影响(上面已提到过)。

代码如下:

import ReactDOM from 'react-dom/client';
import { useState, } from 'react';
const root = ReactDOM.createRoot(document.getElementById("root"));
const Parent = () => {
  const [parentName, setParentName] = useState('Jack')
  const ChildComponent = () => {
    console.log('Init child Component.')
    return <>
      <div>I am child.</div>
    </>
  }
  return <>
    <div>
      <span>Parent name: {parentName}</span>
      <button onClick={() => setParentName('Tony')}>Change Parent'name.</button>
    </div>
    <ChildComponent/>
  </>
}
root.render(
  <Parent/>
);

关系如图:
在这里插入图片描述

效果:
请添加图片描述

2.2 state

state 数据发生变化时,那些引用过 state 的组件都会发生 re-render

提示: 其实和 parent 没啥两样,上面的 parent 演示只是为了加深理解 re-render 的渲染机制。

下面我将以子组件自身更改 state 来完成自身 re-render 的演示。

代码如下:

import ReactDOM from 'react-dom/client';
import { useState, } from 'react';
const root = ReactDOM.createRoot(document.getElementById("root"));
const Parent = () => {
  const [parentName, setParentName] = useState('Jack')
  const ChildComponent = () => {
    console.log('Init child Component.')
    return <>
      <div>I am child.</div>
      <button onClick={() => setParentName('Tony')}>A child can change his parent'name.</button>
    </>
  }
  return <>
    <div>
      <span>Parent name: {parentName}</span>
    </div>
    <ChildComponent/>
  </>
}
root.render(
  <Parent/>
);

关系如图:
在这里插入图片描述
效果:
请添加图片描述

2.3 props

当组件的 props 数据发生变化时触发 re-render

代码如下:

import ReactDOM from 'react-dom/client';
import { useState, } from 'react';
const root = ReactDOM.createRoot(document.getElementById("root"));
const Parent = () => {
  const [parentName, setParentName] = useState('Jack')
  const ChildComponent = (props) => {
    console.log('Init child Component.')
    return <>
      <div>I am child. My parent'name is {props.parentName}</div>
    </>
  }
  return <>
    <div>
      <span>Parent name: {parentName}</span>
      <button onClick={() => setParentName('Tony')}>Change Parent'name.</button>
    </div>
    <ChildComponent parentName={parentName}/>
  </>
}
root.render(
  <Parent />
);

关系如图:
在这里插入图片描述

你可能注意到了,这触发 re-render 还不是因为更改了 state ? 好像跟上面的例子没啥两样啊?
因为 props 本身就支持传递 state 呀,这块先别着急,目前先把它当成 props 发生变化时,组件会执行 re-render 即可 ,等到后面的避免 re-render 章节自然就明白了。

效果:
请添加图片描述

2.4 context

当 context 数据发生变化时,那些引用 context 的组件都会触发 re-render

代码如下:

import ReactDOM from 'react-dom/client';
import { useState, createContext, useContext, } from 'react';
const root = ReactDOM.createRoot(document.getElementById("root"));
const ThemeContext = createContext(null)
const Parent = () => {
  const [theme, setTheme] = useState({
    mode: 'light'
  })
  const ChildComponent = () => {
    const theme = useContext(ThemeContext)
    console.log('Init child component.')
    return <>
      <div>
        The current mode is: {theme.mode}
      </div>
    </>
  }
  const onChangeTheme = () => {
    setTheme(() => ({
      ...theme,
      mode: theme.mode === 'light' ? 'dark' : 'light'
    }))
  }
  return <>
    <ThemeContext.Provider value={theme}>
      <button onClick={onChangeTheme}>Change mode</button>
      <ChildComponent/>
    </ThemeContext.Provider>
  </>
}
root.render(
  <Parent />
);

关系如图:
在这里插入图片描述

你可能注意到了,这触发 re-render 还不是因为更改了 state ? 好像跟上面的例子没啥两样啊?
因为 context 本身就支持传递 state 呀,这块先别着急,目前先把它当成 context 发生变化时,组件会执行 re-render 即可 ,等到后面的避免 re-render 章节自然就明白了。

效果:
请添加图片描述

提示:还没用过 context 的可以先阅读React & 解释常见的 hooks: useState / useRef / useContext / useReducer 再回头看本文。

三、如何优雅地避免 re-render

前言:在理解 re-render 的触发场景后,对如何避免 re-render 将会是一件既有趣又有挑战性的事情。
“挑战性”皆指容易犯“过度/错误 地避免 re-render,这是每个新手基本都会犯的错甚至老手,对于刚学 React 的同学也不必担心,因为它并不是一俩天就能搞定的,得不断的实践和积累才能慢慢减少。

废话不多说,咋们开始进入正文。

3.1 提取 state

既然 state 发生变化时,组件自身和 children 都会 re-render ,我们可以把变更 state 代码单独抽取一个组件来维护。

代码如下:

import ReactDOM from 'react-dom/client';
import { useState, } from 'react';
const root = ReactDOM.createRoot(document.getElementById("root"));
const Parent = () => {
  const BaseComponent = () => {
    const [parentName, setParentName] = useState('Jack')
    return <>
      <div>
        <span>Parent name: {parentName}</span>
        <button onClick={() => setParentName('Tony')}>Change Parent'name.</button>
      </div>
    </>
  }
  const ChildComponent = () => {
    console.log('Init child Component.')
    return <>
      <div>I am child.</div>
    </>
  }
  return <>
    <BaseComponent />
    <ChildComponent/>
  </>
}
root.render(
  <Parent />
);

关系如图:
在这里插入图片描述
效果:
请添加图片描述

3.2 组件以 props / children 的方式传递

children 其实是 props 的一部分,而 props 的其它字段也可作为组件,因此我将它俩单独放一起做演示。

回到正题,在不想分离 state 的情况下,我们可以将组件以 props 的形式传递,一旦 state 发生变化时,若组件自身的 props 未发生变化就不会触发 re-render,本质上也是一种抽离的思想。

代码如下:

import ReactDOM from 'react-dom/client';
import { useState, } from 'react';
const root = ReactDOM.createRoot(document.getElementById("root"));
// 方式1:采用 children 作为组件。
import ReactDOM from 'react-dom/client';
import { useState, } from 'react';
const root = ReactDOM.createRoot(document.getElementById("root"));
const Parent = () => {
  const BaseComponent = ({children}) => {
    const [parentName, setParentName] = useState('Jack')
    return <>
      <div>
        <span>Parent name: {parentName}</span>
        <button onClick={() => setParentName('Tony')}>Change Parent'name.</button>
      </div>
      {children}
    </>
  }
  const ChildComponent = () => {
    console.log('Init child Component.')
    return <>
      <div>I am child.</div>
    </>
  }
  return <>
    <BaseComponent>
      <ChildComponent/>
    </BaseComponent>
  </>
}
root.render(
  <Parent />
);

// 方式2:采用 props 其它字段作为组件传递。
const Parent = () => {
  const BaseComponent = ({ bottom }) => {
    const [parentName, setParentName] = useState('Jack')
    return <>
      <div>
        <span>Parent name: {parentName}</span>
        <button onClick={() => setParentName('Tony')}>Change Parent'name.</button>
      </div>
      {bottom}
    </>
  }
  const ChildComponent = () => {
    console.log('Init child Component.')
    return <>
      <div>I am child.</div>
    </>
  }
  return <>
    <BaseComponent bottom={ChildComponent()}>
    </BaseComponent>
  </>
}
root.render(
  <Parent />
);

关系如图:
在这里插入图片描述
效果:
请添加图片描述

3.3 使用 memo + useMemo/useCallback

memo 用于缓存组件,useMemo 用于缓存数据,useCallback 用于缓存函数。

提示1: useMemo 也能模拟 useCallback ,但俩者的用法有着细微差别,后面会提到。
提示2:memo 的底层实现逻辑也需要维护成本,建议在不能使用 3.1、3.2 的方式情况下再来考虑 memo

3.3.1 memo

当我们无法让组件分离 state 或以 props/children 的方式时,memo 可以派上用场,当组件内引用 props/state/context 发生变化时才会发生re-render

代码如下:

import ReactDOM from 'react-dom/client';
import { useState, memo } from 'react';
const root = ReactDOM.createRoot(document.getElementById("root"));
const ChildMemo = memo(function ChildMemo({ name }) {
  console.log('Init component.')
  return <>
    <h3>Child's name is: {name}</h3>
  </>
})
const Parent = () => {
  const [childName, setChildName] = useState('Lucy')
  const [parentName, setParentName] = useState('Jack')
  return <>
    <div>
      <div>
        <input type="text" value={parentName} placeholder='Please enter your name' onChange={(e) => setParentName(e.target.value)} />
      </div>
      <div>
        <button onClick={() => setChildName('Tony')}>Set child name</button>
      </div>
    </div>
    <ChildMemo name={childName} />
  </>
}
root.render(
  <Parent />
);

关系如图:
在这里插入图片描述

效果:
请添加图片描述

3.3.2 useMemo

上面讲的是缓存组件,那在什么情况下需要缓存数据呢?先思考下这段代码:
在这里插入图片描述
知道引用类型的同学应该都知道,当声明两个对象(数据相同)进行比较时,
obj1 === obj2 总是等于 false,于是每次 re-render 的时候,这个函数实际上就会被重新创建一次,因此这里的 memo 将会一直无效。

请添加图片描述

解决方法就是使用 useMemo 来缓存这份对象:
在这里插入图片描述
其中 useCallback 的第二个参数是可选项,可以指定某个依赖项发生变化时再触发 re-render

具体代码如下:

import ReactDOM from 'react-dom/client';
import { useState, memo, useMemo } from 'react';
const root = ReactDOM.createRoot(document.getElementById("root"));
const ProfileMemo = memo(function Profile({info}) {
  console.log('Init component')
  return <>
    <div>
      Address: {info.address}
    </div>
  </>
})

const Parent = () => {
  const [parentName, setParentName] = useState('Jack')
  const info = useMemo(() => ({
    address: 'London'
  }), [])
  return <>
    <div>Parent name: {parentName}</div>
    <button onClick={() => setParentName('John')}>Change parent's name</button>
    <ProfileMemo info={info} />
  </>
}
root.render(
  <Parent />
);

效果:
请添加图片描述

3.3.3 useCallback

在什么情况下需要缓存函数呢?首先得明确一点,函数也是对象的一部分,因此两个同样的函数比较时永远为 false ,思考下这段代码:
在这里插入图片描述
这里的 ProfileMemo 组件将因为函数的不等价导致再次被 re-render
请添加图片描述

此时缓存函数 useCallback 就可以解决啦,改造后关系图如下:
在这里插入图片描述
代码如下:

import ReactDOM from 'react-dom/client';
import { useState, memo, useMemo, useCallback } from 'react';
const root = ReactDOM.createRoot(document.getElementById("root"));
const ProfileMemo = memo(function Profile({info}) {
  console.log('Init component')
  return <>
    <div>
      Address: {info.address}
    </div>
  </>
})

const Parent = () => {
  const [parentName, setParentName] = useState('Jack')
  const info = useMemo(() => ({
    address: 'London'
  }), [])
  const handSubmit = useCallback(() => {
    console.log('Save profile.')
  }, [])
  return <>
    <div>Parent name: {parentName}</div>
    <button onClick={() => setParentName('John')}>Change parent's name</button>
    <ProfileMemo handSubmit={handSubmit} info={info} />
  </>
}
root.render(
  <Parent />
);

useCallback 的第二个参数和 useMemo 是一样的,可以指定某个依赖项发生变化时再触发 re-render

效果:
请添加图片描述

3.3.4 useMmeo 模拟 useCallback

useMemo 除了缓存数据也支持缓存函数,以下两种写法的作用是一样的:

const handleSumit = useMemo(() => (
	return () => {
		console.log('Save profile')	
	}
), [])
const handSubmit = useCallback(() => {
    console.log('Save profile.')
  }, [])

唯一区别是, useMemo 共包裹两层函数,官方也建议 useCallback 作为缓存函数首选。

3.4 提取 context

经过前几章的学习,对如何避免 re-render 我们其实已经有了一个框架和概念。
比如 3.1 章节,我们也可以将变化 context 的部分提取出来,与其它组件保持隔离。

具体效果不演示了,大家可自行练习。

四、避免 re-render 的几个注意事项

4.1 memo 不要与变更父级 state 数据的子组件放一起

思考下这段代码:当子组件更改外部的 state 时,ChildMemo 组件是否会触发 re-render?
在这里插入图片描述

答案是会的!而且 input 的每一次更改外部 state 时,ChildMemo 都将被重新 re-render

为什么?

这里会涉及到作用域的概念,当前 BaseCmpstate 并不在同个作用域,它改变了外部 state 作用域的值,
根据 2.2 章节讲到,当 state 数据发生变化时,那些引用过 state 的组件都将被 re-render,而 ChildMemo 刚好与 BaseCmp 处于同个作用域,因此也就一并被 re-render

解决的方式有几种:

  1. 将 ChildMemo 提取出来,与 BaseCmp 隔离。
    在这里插入图片描述

  2. 将外部的 state 转成内部 state ,保持同级作用域。
    在这里插入图片描述

  3. 自行发挥~

结论:同一作用域的 state 发生变化时,memo 在没有引入该 state 的情况下不会受到影响,
若 memo 放在引入外部 state 的子组件时,当 state 发生变化时则受到影响,它并不能阻止 re-render

以上便是有关 re-render 的所有内容,如有问题,欢迎补充。


完!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2160670.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

获取商品销量详情API:深入解析返回值,助力电商决策

在电商行业&#xff0c;了解商品的销量详情对于商家制定营销策略、优化库存管理和提升用户体验至关重要。通过调用获取商品销量详情的API接口&#xff0c;商家可以实时获取关键的销售数据&#xff0c;从而做出更加明智的决策。本文将深入解析获取商品销量详情API的返回值&#…

linux信号| 学习信号三步走 | 学习信号需要打通哪些知识脉络?

前言: 本节内容主要讲解linux下信号的预备知识以及信号的概念&#xff0c; 信号部分我们将会分为几个阶段进行讲解&#xff1a;信号的概念&#xff0c; 信号的产生&#xff0c; 信号的保存。本节主要讲解信号 ps:本节内容适合学习了进程相关概念的友友们进行观看哦 目录 什么是…

轻松重置 MySQL 8.0 Root 密码的简便方法!

在Windows环境下安装MySQL数据后&#xff0c;如果忘记了 MySQL 8.0 的 root 密码&#xff0c;不必担心&#xff01;通过 --skip-grant-tables 和 named-pipe 模式登录后&#xff0c;只需几步简单的 SQL 命令即可重置密码&#xff1a;刷新权限表、修改密码、再刷新权限&#xff…

SpringBoot+Thymeleaf租房管理系统

> 这是一个基于SpringBootThymeleafBootstrap实现的租房管理系统。 > 功能比较完善&#xff0c;包括用户注册/登录、房源登记、账单费用配置、统计报告等功能。 > 模拟真实使用环境&#xff0c;包括了自然人与法人的身份证明录入、房产证信息录入、通过邮件推送月…

E2VPT: An Effective and Efficient Approach for Visual Prompt Tuning

论文汇总 存在的问题 1.以前的提示微调方法那样只关注修改输入&#xff0c;而应该明确地研究在微调过程中改进自注意机制的潜力&#xff0c;并探索参数效率的极限。 2.探索参数效率的极值来减少可调参数的数量? 解决办法 提示嵌入进行transformer中 提示剪枝 Token-wise …

学生网上选课系统设计与实现

学生网上选课系统设计与实现 摘 要 传统办法管理信息首先需要花费的时间比较多&#xff0c;其次数据出错率比较高&#xff0c;而且对错误的数据进行更改也比较困难&#xff0c;最后&#xff0c;检索数据费事费力。因此&#xff0c;在计算机上安装学生网上选课系统软件来发挥其…

托盘检测系统源码分享

托盘检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer Vision …

两数之和、三数之和、四数之和

目录 两数之和 题目链接 题目描述 思路分析 代码实现 三数之和 题目链接 题目描述 思路分析 代码实现 四数之和 题目链接 题目描述 思路分析 代码实现 两数之和 题目链接 LCR 179. 查找总价格为目标值的两个商品 - 力扣&#xff08;LeetCode&#xff09; 题目…

牛客周赛 Round 61 (C++实现)

比赛链接&#xff1a;牛客竞赛_ACM/NOI/CSP/CCPC/ICPC算法编程高难度练习赛_牛客竞赛OJ (nowcoder.com) 文章目录 1.致十年后的我们1.1 题目描述1.2 思路1.3 代码 2.简单图形问题2.1 题目描述2.2 思路2.3 代码 3. 小红的机器人构造3.1 题目描述3.2 思路3.2.1 问题13.2.2 问题23…

力扣 中等 1901.寻找峰值II

文章目录 题目介绍题解 题目介绍 题解 需要明白一个事实&#xff1a;从任意一个点出发&#xff0c;可以经过一个递增路径&#xff0c;找到一个极大值点。 求出一行的最大值&#xff0c;如果这行最大值比上面的要小&#xff0c;那峰值&#xff08;之一&#xff09;就会在上面 …

最具成长潜力奖!2024中国互联网发展创新与投资大赛(深圳)落幕,爱加密载誉而归!

近日&#xff0c;由中央网信办信息化发展局、广东省委网信办指导&#xff0c;中国互联网发展基金会、中国互联网投资基金、深圳市委网信办联合主办的2024中国互联网发展创新与投资大赛&#xff08;深圳&#xff09;正式落幕。 本次大赛共有508个项目报名参赛&#xff0c;经过4个…

springboot中的异步任务

在springboot项目中可以通过EnableAsyncAsync的方式简化异步操作&#xff0c;下文使用springboot:3.2.1 源码分析 若一个bean中的公共方法上标注了Async&#xff0c;在系统启动时&#xff0c;会给这个类创建一个代理对象&#xff0c;并将该代理对象作为bean注册到spring容器中 …

PyTorch 模型调试与故障排除指南

在当代深度学习领域&#xff0c;PyTorch 已成为开发和训练神经网络的主要框架之一。然而随着模型复杂度的增加和数据规模的扩大&#xff0c;开发者常常面临各种调试和优化挑战。本文旨在为 PyTorch 开发者提供一个全面的调试指南&#xff0c;涵盖从基础概念到高级技术的广泛内容…

2024双11买什么东西比较好?2024双十一好物推荐

双十一各种爆款商品&#xff0c;打折商品非常多&#xff0c;容易让人挑花眼也容易落入消费主义的陷阱&#xff0c;买回家后发现并不实用&#xff0c;甚至可能是自己平常生活中根本用不上的东西&#xff01;即使是打折活动也要在购买前考虑一下自己是否需要&#xff0c;在日常生…

DAMODEL——创建云实例与部署深度学习模型的详细指南

使用 DAMODEL 控制台创建云实例与部署深度学习模型的详细指南 本文将逐步介绍如何使用 DAMODEL 控制台创建云实例、配置 SSH 密钥、上传与下载数据集、以及基于 UNet 网络进行眼底血管分割的完整流程。此指南适用于需要在云端进行深度学习训练的用户&#xff0c;提供详细的操作…

YOLOv10改进,YOLOv10主干网络替换为FasterNet(全网独发手把手教学,助力涨点)

摘要 近年来,许多研究致力于减少浮点运算(FLOPs)以加速神经网络。然而,我们观察到这种FLOPs的减少并不一定能带来相应的延迟减少。这主要是由于运算每秒浮点运算数(FLOPS)效率低下,尤其是在频繁的内存访问(如深度卷积)时。为了解决这一问题,提出了一种新的部分卷积(…

如何快速上手一个Github的开源项目

程序研发领域正是有一些热衷开源的小伙伴&#xff0c;技能迭代才能如此的迅速&#xff0c;因此&#xff0c;快速上手一个GitHub上的开源项目&#xff0c;基本上已经变成很个程序员小伙伴必须掌握的技能&#xff0c;因为终究你会应用到其中的一个或多个项目&#xff0c;帮助自己…

<刷题笔记> 二叉搜索树与双向链表注意事项

二叉搜索树与双向链表_牛客题霸_牛客网 (nowcoder.com) 根据题意&#xff0c;我们需要将搜索二叉树转换成有序的形式。 重点一&#xff1a;BST的中序遍历一定是有序的 因此&#xff0c;此题无论如何都需要使用中序。 又因为要求原地算法&#xff0c;所以&#xff1a; 重点二&a…

Oracle AI理论与实践,企业落地篇干货满满

最近也是看到了圈子里的一位DBA好友&#xff0c;领导安排的工作是让负责AI的落地&#xff0c;而且也作为他业绩考核的指标&#xff0c;作为1名15年的DBA老兵来说&#xff0c;让AI落地面临的困难重重。 AI已经逐渐侵入到实际的生活中&#xff0c;最近我也是参加了Oracle官方在中…

大模型下载、本地部署和调用攻略

零、下载大模型——以Qwen/Qwen2.5-7B为例 1、下载前的准备工作 1.1 仔细阅读模型的Model card介绍 里面会有支持的上下文长度、模型结构、参数量等基本信息 1.2 查看模型文件 Tokenizer相关的文件&#xff1a; merges.txt&#xff1a;这是用于Byte Pair Encoding&#xff…