当前位置: 首页 > article >正文

React 性能优化三剑客实战:告别无效重渲染!

在 Vue 中我们可能依赖 Vuex + computed 进行状态共享和性能优化,而在 React 里呢?不需要用 Redux,靠 useContextmemouseMemo 三剑客就能构建高性能组件通信方案!

🧩 useContext 再回顾:状态共享不等于性能优化

上篇问题我们讲了 useContext 实现全局状态管理,但也提到了 useContext 引起的性能问题:

“每当useContext内的数据发生变化,都会导致 所有使用useContext()的组件重新渲染。”

const ThemeContext = createContext('light');function App() {return (<ThemeContext.Provider value="dark"><Toolbar /></ThemeContext.Provider>);
}function Toolbar() {return <ThemedButton />;
}function ThemedButton() {const theme = useContext(ThemeContext);return <button className={theme}>按钮</button>;
}

虽然 useContext 很好用,但它有一个致命的性能问题:只要 Providervalue 改变,所有用到这个 context 的组件都会重新渲染,哪怕组件内部根本没用到变化的值。

useContext 本身并不具备“避免不必要渲染”的能力。

接下来,我们将通过一个实际项目场景,讲解如何组合使用 useMemomemouseContext,从而将一次看似不起眼的性能问题,打磨成用户几乎无感的丝滑体验。


🧩 回顾三剑客

简单回顾下三剑客(想详细了解更多概念的请翻阅之前文章):

🟦 useContext

  • 用于在组件树中共享状态
  • 类似 Vue 的 provide / inject
const ThemeContext = createContext();
const value = useContext(ThemeContext);

🟩 memo

  • 用于缓存组件结构
  • 类似 Vue 的 v-memo + pureComponent 行为
const MemoComponent = memo(Component);

🟨 useMemo

  • 用于缓存变量值或对象引用,避免不必要计算或更新
  • 类似 Vue 的 computed(但是手动依赖)
const memoizedValue = useMemo(() => computeVal(), [deps]);

三剑客作用

Hook作用
useContext跨组件传递状态
memo记忆组件,避免无意义的重新渲染
useMemo记忆复杂数据或对象,避免引用变化导致子组件更新

三者组合的思路是:

“将不会变的值用useMemo缓存,传给Context,再通过memo包裹真正消费这些值的子组件。”

🧩 场景背景

你在开发一个社交 App 的文章详情页,页面中包含:

  • 一篇文章信息
  • 一组评论列表(CommentList
  • 一个点赞按钮(LikeButton

点赞按钮点击后只影响点赞数,但由于组件结构未优化,整个评论列表也会 无意义刷新 —— 这是我们要优化的核心问题。

❌ 优化前代码(每次点赞导致所有子组件刷新)

import { useState } from "react";export default function App() {const [likes, setLikes] = useState(0);const [comments] = useState([{ id: 1, author: "张三", content: "写得真好!" },{ id: 2, author: "李四", content: "受教了!" },]);return (<div><LikeButton likes={likes} onLike={() => setLikes(likes + 1)} /><CommentList comments={comments} /></div>);
}function LikeButton({ likes, onLike }) {return <button onClick={onLike}>👍 点赞 ({likes})</button>;
}function CommentList({ comments }) {console.log("CommentList 渲染");return (<ul>{comments.map((comment) => (<li key={comment.id}><strong>{comment.author}</strong>{comment.content}</li>))}</ul>);
}

痛点分析:

  • 每次点击点赞按钮,父组件 App 重新渲染。
  • 尽管 comments 没有变化,CommentList 仍然重新渲染。

在这里插入图片描述

✅ 简单,用 memo 直接解决

import { useState, memo } from "react";const MemoCommentList = memo(CommentList);export default function App() {const [likes, setLikes] = useState(0);const [comments] = useState([{ id: 1, author: "张三", content: "写得真好!" },{ id: 2, author: "李四", content: "受教了!" },]);return (<div><LikeButton likes={likes} onLike={() => setLikes(likes + 1)} /><MemoCommentList comments={comments} /></div>);
}
...

我们引入 memo,创建 memo 组件 MemoCommentList,此时再看:

在这里插入图片描述

哦豁!?这就解决了?如此简单?

此时一位热心网友 JiangJiang 出现了,他也想来发表评论:

此时评论区来了位 帅哥-JiangJiang,他也想发表评论:

import { useState, memo, createContext, useContext } from "react";const CommentContext = createContext(null);
const MemoCommentList = memo(CommentList);export default function App() {const [likes, setLikes] = useState(0);const [comments, setComments] = useState([{ id: 1, author: "张三", content: "写得真好!" },{ id: 2, author: "李四", content: "受教了!" },]);return (<div><CommentContext.Provider value={{ comments, setComments }}><LikeButton likes={likes} onLike={() => setLikes(likes + 1)} /><MemoCommentList /></CommentContext.Provider></div>);
}function LikeButton({ likes, onLike }) {return <button onClick={onLike}>👍 点赞 ({likes})</button>;
}function CommentList() {const { comments } = useContext(CommentContext);console.log("CommentList 渲染");return (<><ul>{comments.map((comment) => (<li key={comment.id}><strong>{comment.author}</strong>{comment.content}</li>))}</ul><SendRemark /></>);
}function SendRemark() {const { comments, setComments } = useContext(CommentContext);const handleAdd = () => {const newComment = {id: comments.length + 1,author: "JiangJiang",content: "这篇文章不错!",};setComments([...comments, newComment]);};return <button onClick={handleAdd}>路过的 JiangJiang</button>;
}

补充了下逻辑:

  • 新增 SendRemark 组件给 JiangJiang 发表评论。
  • 组件层级比较深,我们使用 useContext 来进行状态管理。
  • 评论区数据 comments 改为从 context 中获取。

在这里插入图片描述

我们再来看:

在这里插入图片描述

恩,路过的 JiangJiang 发表了评论,导致 comments 数据变更,引起 CommentList 重新渲染,这没问题。

但是…为什么点赞又引起了 CommentList 的重新渲染?刚刚使用 memo 不是已经解决了?

就像我路过评论区,却因为点赞被拉进去挨了一顿重渲染的“毒打”……

问题出现在哪里?就在 context 上:

1.context 对象引用变化

CommentContext.Providervalue 中传递了一个新对象:

value={{ comments, setComments }}

每次 App 组件渲染(比如点赞时 likes 变化),这个对象都会被重新创建,即使内容未变,但引用地址变化了

2.memocontext 无效

memo 只能阻止 props 变化导致的渲染,但 当组件消费的 Context 值变化时(即使内容相同但引用变化),memo 也无法阻止渲染

知道了问题所在,那有什么办法可以缓存整个 Context 的 value 对象,保持引用稳定?

答案就是:useMemo

✅ memo + useMemo + useContext:三剑客联动优化

使用 useMemo 解决上面问题,缓存 value

import { useState, memo, createContext, useContext, useMemo } from "react";const CommentContext = createContext(null);
const MemoCommentList = memo(CommentList);
const useMemoComments = () => useContext(CommentContext);export default function App() {const [likes, setLikes] = useState(0);const [comments, setComments] = useState([{ id: 1, author: "张三", content: "写得真好!" },{ id: 2, author: "李四", content: "受教了!" },]);const memoComments = useMemo(() => ({ comments, setComments }), [comments]);return (<div><CommentContext.Provider value={memoComments}><LikeButton likes={likes} onLike={() => setLikes(likes + 1)} /><MemoCommentList /></CommentContext.Provider></div>);
}function LikeButton({ likes, onLike }) {return <button onClick={onLike}>👍 点赞 ({likes})</button>;
}function CommentList() {const { comments } = useMemoComments();console.log("CommentList 渲染");return (<><ul>{comments.map((comment) => (<li key={comment.id}><strong>{comment.author}</strong>{comment.content}</li>))}</ul><SendRemark /></>);
}function SendRemark() {const { comments, setComments } = useMemoComments();const handleAdd = () => {const newComment = {id: comments.length + 1,author: "JiangJiang",content: "这篇文章不错!",};setComments([...comments, newComment]);};return <button onClick={handleAdd}>路过的 JiangJiang</button>;
}

我们加上了:

  • 使用 useMemo 缓存 context 传入的 value
  • 自定义了 Hook:useMemoComments,方便导出数据。

此时就大功告成了,改变数据也不会引起不相关组件更新:

在这里插入图片描述

相对比优化前的代码,我们做了什么?

  • 使用 memo 保证子组件只在真正依赖的数据变化是才更新。
  • 配合 useContext 进行全局状态管理。
  • 使用 useMemo 缓存 context value 值,解决不相关组件也随数据更新问题。

🎯 优化效果对比

优化前

在这里插入图片描述

优化后

在这里插入图片描述


🧠 总结:三者配合的心法

useContext 虽然让跨层级传参变得优雅,但它对性能的 副作用 常常被忽视:只要 value 的引用发生变化,所有消费它的组件都会 无差别更新,哪怕这些组件并未使用发生变化的那部分值。

本文通过一个点赞 + 评论的实战场景,详细剖析了这种性能问题的根源,并通过以下三种方式给出了解决方案:

  1. useMemo 保持 value 稳定:避免不必要的引用变化;
  2. memo 缓存消费组件:提升组件重用率,减少重渲染;
  3. 组件职责拆分:精细控制渲染粒度,配合 memo 更高效。

当这三种策略协同配合时,能最大化发挥 React 的性能潜力,实现真正意义上的 按需渲染

希望这篇文章能帮你更深入理解 useContext 背后的运行机制,在写业务时也能写出更高性能、更易维护的代码,如果你觉得有帮助,别忘了点个赞👍或关注我后续的 重学 React 系列!。

相关文章:

React 性能优化三剑客实战:告别无效重渲染!

在 Vue 中我们可能依赖 Vuex computed 进行状态共享和性能优化&#xff0c;而在 React 里呢&#xff1f;不需要用 Redux&#xff0c;靠 useContext、memo、useMemo 三剑客就能构建高性能组件通信方案&#xff01; &#x1f9e9; useContext 再回顾&#xff1a;状态共享不等于性…...

深度学习3.3 线性回归的简洁实现

步骤操作作用前向计算net(X)计算预测值 y_hat Xw b损失计算loss(y_hat, y)量化预测误差&#xff0c;驱动参数更新反向传播l.backward()计算参数梯度参数更新trainer.step()根据梯度调整参数&#xff0c;逼近最优解梯度清零trainer.zero_grad()防止梯度累积&#xff08;必须放…...

复盘20250422

深度分析及个股推荐 1. 行业前景与个股逻辑梳理 从提供的股票信息来看&#xff0c;主要涉及以下行业&#xff1a;合成尼古丁&#xff08;电子烟&#xff09;、化工、跨境支付、跨境电商、农药、食品饮料、光刻机、电子商务、造纸等。需结合行业景气度、政策支持、公司核心竞争…...

从零开始学习MySQL的系统学习大纲

文章目录 前言第一阶段&#xff1a;数据库与 MySQL 基础认知数据库基础概念MySQL 简介 第二阶段&#xff1a;MySQL 安装与环境搭建安装前的准备MySQL 安装过程安装后的配置 第三阶段&#xff1a;SQL 基础语法SQL 概述数据库操作数据表操作数据操作 第四阶段&#xff1a;SQL 高级…...

APP动态交互原型实例|墨刀变量控制+条件判断教程

引言 不同行业的产品经理在绘制原型图时&#xff0c;拥有不同的呈现方式。对于第三方软件技术服务公司的产品经理来说&#xff0c;高保真动态交互原型不仅可以在开发前验证交互逻辑&#xff0c;还能为甲方客户带来更直观、真实的体验。 本文第三部分将分享一个实战案例&#…...

基于控制台的小车导航游戏开发详解(C++实现)

本文将详细讲解一个基于C控制台的小车导航游戏项目。通过该项目可以学习二维数组操作、队列数据结构应用以及游戏循环控制等核心编程概念&#xff0c;特别适合刚接触游戏开发的初学者学习。 一、项目概述 1.1 游戏规则 玩家可创建多辆具有不同初始位置和移动速度的小车 每辆…...

色谱图QCPColorMap

一、QCPColorMap 概述 QCPColorMap 是 QCustomPlot 中用于绘制二维颜色图的类&#xff0c;可以将矩阵数据可视化为颜色图&#xff08;热力图&#xff09;&#xff0c;支持自定义色标和插值方式。 二、主要属性 属性类型描述dataQCPColorMapData存储颜色图数据的对象interpol…...

大文件分片上传进阶版(新增md5校验、上传进度展示、并行控制,智能分片、加密上传、断点续传、自动重试),实现四位一体的网络感知型大文件传输系统‌

上篇文章我们总结了大文件分片上传的主要核心&#xff0c;但是我对md5校验和上传进度展示这块也比较感兴趣&#xff0c;所以在deepseek的帮助下&#xff0c;扩展了一下我们的代码&#xff0c;如果有任何问题和想法&#xff0c;非常欢迎大家在评论区与我交流&#xff0c;我需要学…...

oracle不同数据库版本的自增序列

-- 查看数据库版本 SELECT * FROM v$version WHERE banner LIKE Oracle%; 1. Oracle 12c及以上版本支持 id NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, id NUMBER GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1) PRIMARY KEY, -- 语法 id NUMBER GENER…...

【KWDB创作者计划】_针对KWDB时序数据库(多副本集群环境)进行压力测试

【KWDB创作者计划】_针对KWDB时序数据库&#xff08;多副本集群环境&#xff09;进行压力测试 1. 概述2. 压测环境部署3. 生成测试数据4. 写入性能测试5. 查询性能测试7. 总结 1. 概述 KaiwuDB分布式多模数据库从物联网场景真实需求出发&#xff0c;针对性设计多模架构。物联网…...

极狐GitLab 自定义实例级项目模板功能介绍

极狐GitLab 是 GitLab 在中国的发行版&#xff0c;关于中文参考文档和资料有&#xff1a; 极狐GitLab 中文文档极狐GitLab 中文论坛极狐GitLab 官网 自定义实例级项目模板 (PREMIUM SELF) 极狐GitLab 管理员可以将群组设置为在实例上创建新项目时可选择的项目模板的来源。然…...

最新扣子(Coze)案例教程:飞书多维表格按条件筛选记录 + 读取分页Coze工作流,无限循环使用方法,手把手教学,完全免费教程

大家好&#xff0c;我是斜杠君。 &#x1f468;‍&#x1f4bb; 星球群里有同学想学习一下飞书多维表格的使用方法&#xff0c;关于如何通过按条件筛选飞书多维表格中的记录&#xff0c;以及如何使用分页解决最多一次只能读取500条的限制问题。 斜杠君今天就带大家一起搭建一…...

第八天 AI开发:NavMesh导航系统 对话系统:使用ScriptableObject存储对话数据 存档系统:JSON序列化保存数据

一、智能导航系统&#xff1a;NavMesh实战指南 1.1 导航网格基础配置 在Unity编辑器中&#xff1a; 选择场景中的静态物体勾选Navigation Static属性打开Window > AI > Navigation窗口 烘焙参数设置&#xff1a; NavMeshBuildSettings settings NavMesh.GetSettingsBy…...

Spring AI Alibaba-02-多轮对话记忆、持久化消息记录

Spring AI Alibaba-02-多轮对话记忆、持久化消息记录 Lison <dreamlison163.com>, v1.0.0, 2025.04.19 文章目录 Spring AI Alibaba-02-多轮对话记忆、持久化消息记录多轮对话对话持久-Redis 本次主要聚焦于多轮对话功能的实现&#xff0c;后续会逐步增加更多实用内容&…...

联邦元学习实现个性化物联网的框架

随着数据安全和隐私保护相关法律法规的出台&#xff0c;需要直接在中央服务器上收集和处理数据的集中式解决方案&#xff0c;对于个性化物联网而言&#xff0c;训练各种特定领域场景的人工智能模型已变得不切实际。基于此&#xff0c;中山大学&#xff0c;南洋理工大学&#xf…...

做虚拟化应该怎么选择美国服务器?

选择适合做虚拟化的美国服务器&#xff0c;需要综合考虑硬件性能、网络质量、稳定性、价格和服务支持等多个方面。以下是详细的选购指南&#xff0c;适合准备搭建VPS、虚拟主机、分销业务、开发测试环境、容器集群等用途的用户参考。 一、为什么美国服务器适合虚拟化? 美国机房…...

实验1 温度转换与输入输出强化

知识点&#xff1a;input()/print()、分支语句、字符串处理&#xff08;教材2.1-2.2&#xff09; 实验任务&#xff1a; 1. 实现摄氏温度与华氏温度互转&#xff08;保留两位小数&#xff09; 2. 扩展功能&#xff1a;输入错误处理&#xff08;如非数字输入提示重新输入&#x…...

MongoDB 集合名称映射问题

项目场景 在使用 Spring Data MongoDB 进行开发时&#xff0c;定义了一个名为 CompetitionSignUpLog 的实体类&#xff0c;并创建了对应的 Repository 接口。需要明确该实体类在 MongoDB 中实际对应的集合名称是 CompetitionSignUpLog 还是 competitionSignUpLog。 问题描述 …...

【AI】SpringAI 第五弹:接入千帆大模型

1. 添加依赖 <dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-starter-model-qianfan</artifactId> </dependency> 2. 编写 yml 配置文件 spring:ai:qianfan:api-key: 你的api-keysecret-key: 你的secr…...

【编码规范】原生开发 与 Vue+组件库开发

原生开发 vs Vue组件库开发对比 一、原生开发常用方法 DOM操作&#xff1a; document.getElementById()document.querySelector()element.addEventListener()classList API操作类名 事件处理&#xff1a; 直接事件绑定事件委托 document.body.addEventListener(click, functi…...

[Godot] C#2D平台游戏基础移动和进阶跳跃代码

本文章给大家分享一下如何实现基本的移动和进阶的跳跃&#xff08;跳跃缓冲、可变跳跃、土狼时间&#xff09;以及相对应的重力代码&#xff0c;大家可以根据自己的需要自行修改 实现效果 场景搭建 因为Godot不像Unity&#xff0c;一个节点只能绑定一个脚本&#xff0c;所以我…...

【Unity笔记】Unity + OpenXR项目无法启动SteamVR的排查与解决全指南

图片为AI生成 一、前言 随着Unity在XR领域全面转向OpenXR标准&#xff0c;越来越多的开发者选择使用OpenXR来构建跨平台的VR应用。但在项目实际部署中发现&#xff1a;打包成的EXE程序无法正常启动SteamVR&#xff0c;或者SteamVR未能识别到该应用。本文将以“Unity OpenXR …...

使用 rebase 轻松管理主干分支

前言 最近遇到一个技术团队的 dev 环境分支错乱&#xff0c;因为是多人合作大家各自提交信息&#xff0c;导致出现很多交叉合并记录&#xff0c;让对应 log 看起来非常混乱&#xff0c;难以阅读。 举例说明 假设我们有一个项目&#xff0c;最初develop分支有 3 个提交记录&a…...

【愚公系列】《Python网络爬虫从入门到精通》063-项目实战电商数据侦探(主窗体的数据展示)

&#x1f31f;【技术大咖愚公搬代码&#xff1a;全栈专家的成长之路&#xff0c;你关注的宝藏博主在这里&#xff01;】&#x1f31f; &#x1f4e3;开发者圈持续输出高质量干货的"愚公精神"践行者——全网百万开发者都在追更的顶级技术博主&#xff01; &#x1f…...

HttpSessionListener 的用法笔记250417

HttpSessionListener 的用法笔记250417 以下是关于 HttpSessionListener 的用法详解&#xff0c;涵盖核心方法、实现步骤、典型应用场景及注意事项&#xff0c;帮助您全面掌握会话&#xff08;Session&#xff09;生命周期的监听与管理&#xff1a; 1. 核心功能 HttpSessionLi…...

火山RTC 5 转推CDN 布局合成规则

实时音视频房间&#xff0c;转推CDN&#xff0c;文档&#xff1a; 转推直播--实时音视频-火山引擎 一、转推CDN 0、前提 * 在调用该接口前&#xff0c;你需要在[控制台](https://console.volcengine.com/rtc/workplaceRTC)开启转推直播功能。<br> * 调…...

Spark两种运行模式与部署

1. Spark 的运行模式 部署Spark集群就两种方式&#xff0c;单机模式与集群模式 单机模式就是为了方便开发者调试框架的运行环境。但是生产环境中&#xff0c;一般都是集群部署。 现在Spark目前支持的部署模式&#xff1a; &#xff08;1&#xff09;Local模式&#xff1a;在本地…...

react 父子组件通信 子 直接到父, 父 forwardref子

React核心概念&#xff1a;单向数据流&#xff08;Unidirectional Data Flow&#xff09; React 中数据的流动像瀑布一样&#xff0c;只能从上层组件&#xff08;父组件&#xff09;流向下层组件&#xff08;子组件&#xff09;。 子组件无法直接反向修改父组件的数据&#x…...

qt画一朵花

希望大家的生活都更加美好&#xff0c;画一朵花送给大家 效果图 void FloatingArrowPubshButton::paintEvent(QPaintEvent *event) {QPainter painter(this);painter.setRenderHints(QPainter::Antialiasing);QPen pen;pen.setColor("green");pen.setWidth(5);QBrush…...

服务器上安装maven

1.安装 下载安装包 https://maven.apache.org/download.cgi 解压安装包 cd /opt/software tar -xzvf apache-maven-3.9.9-bin.tar.gz 安装目录(/opt/maven/) mv /opt/software/apache-maven-3.9.9 /opt/ 3.权限设置 把/opt/software/apache-maven-3.9.9 文件夹重命名为ma…...