停止追逐 React 重渲染

大多数开发者都在浪费时间对抗多余的重渲染。真正的 React 架构师根本让问题无从产生——下面就来揭开他们的思路,以及为何大多数所谓的性能优化技巧反而拖慢了你的应用。


重渲染的无尽轮回

先来直击痛点:如果还在项目里到处撒 useMemouseCallback,却依然被卡顿困扰,接下来的内容务必深读。

无数人在出现卡顿后,第一反应都是:追踪渲染次数→猛贴优化钩子→结果性能提升微乎其微→无限循环。实际上,重渲染只是“发烧”,真正的病灶在于设计层面的结构缺陷


四大隐藏性能杀手

经手数十个大中型 React 项目,以下四类反模式始终如影随形,让重渲染浪潮肆虐。

1. 全局状态泛滥

“什么都往 Redux 丢”往往是成本最高的建议。

  • 问题:全局状态一变动,所有订阅组件都要检查更新。

  • 案例:某电商项目中,购物车中一个价格变更竟触发了 30 多个完全无关组件的重新渲染。

  • 对策:仅对真正全局的数据(如用户认证)使用全局状态,其它 UI 状态尽量放到局部组件或更贴近使用场景的上层组件里。

2. 过度传参(Prop Drilling)

深层组件链上反复传递 props,看似显式却会形成“瀑布效应”。

  • 问题:顶层过滤条件变化,一连串子孙组件都被迫重新渲染。

  • 案例:某仪表盘项目,切换一次筛选就导致 50+ 组件渲染,唯独图表组件才真正用到那条数据。

  • 对策:介于全局状态与深度传参之间,沿功能模块中层节点建立局部 Context,真正做到只触发相关区域重渲染。

3. Context 一锅端

把所有状态都放进一个 Context 看似方便,运行时账单才会让人心塞。

  • 问题:Context 更新会让所有使用该 Context 的组件都重渲染。

  • 案例:某金融看板里将用户数据、设置、实时行情都塞进同一 Context,导致行情每次波动时,设置面板也跟着刷新。

  • 对策:拆分 Context——按领域(用户、UI、数据)或按更新频率(静态 vs. 动态)建立多个小 Context。

4. Key 用错位

看似不起眼,却能让 React 重拆 DOM 而非局部更新。

  • 问题:使用数组索引做 key,会在列表重排时强制销毁并重建所有子组件。

  • 案例:某客户的列表拖拽效果卡顿几秒,排查后发现正是索引 key 导致的整个列表重渲染。

  • 对策:务必用稳定且唯一的标识(如数据库主键)作为 key,保证 React 精确复用组件。


五步性能制胜法则

真正的高手从不事后追渲染,而是从架构层面预防。以下五条策略,能让应用从一开始就高效运行。

1. 状态贴近使用场景

  • 原则:把 state 放在最近公共祖先

  • 实践:将全局存储中的小型 UI 状态(展开/收起、选中项)转移到相应组件内部或更低层次的父组件。

2. 有的放矢地 Memo 化

  • 原则:只对真正昂贵且频繁执行的计算或组件使用 memouseMemouseCallback

  • 实践:先通过 Profiling 确定性能瓶颈,再集中优化;避免对简单字符串或小数组做无谓 memo。

3. Context 切片

  • 原则:用多个小 Context 取代一个巨 Context。

  • 实践:按功能域(如 AuthContext、MarketDataContext)和更新频率拆分上下文,确保微小更新不会连累无关组件。

4. 精准数据选择器

  • 原则:组件只订阅所需数据切片。

  • 实践:在 Redux 中用 useSelector 精选字段;在 Context 中封装自定义选择钩子,只对必要数据做依赖。

5. Profile-First 开发

  • 原则测量胜于臆断

  • 实践:把 React DevTools Profiler 当做日常工具;遇到卡顿先 Profile,再针对最耗时的渲染链条下手;借助 why-did-you-render 即时揭示多余渲染。


重构前后对比(一瞥)

重度耦合的 Todo 应用(重渲染灾难版)

function TodoApp() {const [todos, setTodos] = useState([]);const [filter, setFilter] = useState('all');// 每次 render 都重建这些函数const addTodo = () => { /* ... */ };const toggleTodo = id => { /* ... */ };const filtered = todos.filter(/* 多次执行 */);return (<>{filtered.map(todo => (<TodoItemkey={todo.id}text={todo.text}onToggle={() => toggleTodo(todo.id)}/>))}<Stats count={todos.length} /></>);
}

分层拆解后的高性能版

// 顶层只负责状态管理
function TodoApp() {const [todos, setTodos] = useState([]);const [filter, setFilter] = useState('all');const addTodo = useCallback(text => { /* 稳定引用 */ }, []);const toggleTodo = useCallback(id => { /* 稳定引用 */ }, []);return (<><AddTodoForm onAdd={addTodo} /><FilterControls filter={filter} onChange={setFilter} /><TodoList todos={todos} filter={filter} onToggle={toggleTodo} /><TodoStats todos={todos} /></>);
}// 子组件按需 memo 和 useMemo
const TodoList = memo(({ todos, filter, onToggle }) => {const filtered = useMemo(() =>todos.filter(/* ... */),[todos, filter]);return filtered.map(todo => (<TodoItem key={todo.id} todo={todo} onToggle={onToggle} />));
});

实战立刻可用的七条锦囊

  1. 耗时逻辑放进 useEffect避免在渲染阶段执行重计算,改为渲染后再执行:

    // 错误示范:阻塞渲染
    function MyComponent({ data }) {const result = heavyCompute(data);return <div>{result}</div>;
    }// 优化后:在 useEffect 中执行
    function MyComponent({ data }) {const [result, setResult] = useState(null);useEffect(() => {const res = heavyCompute(data);setResult(res);}, [data]);if (result === null) return <div>Loading...</div>;return <div>{result}</div>;
    }
  2. 列表虚拟化对于长列表,只渲染可视区域,推荐用 react-window 或 react-virtualized

    import { FixedSizeList as List } from 'react-window';function VirtualizedList({ items }) {return (<Listheight={500}itemCount={items.length}itemSize={50}width="100%">{({ index, style }) => (<div style={style}>{items[index]}</div>)}</List>);
    }
  3. 输入防抖对于搜索、过滤等高频输入,使用防抖减少无效请求:

    import { useState, useEffect } from 'react';function useDebounce(value, delay) {const [debounced, setDebounced] = useState(value);useEffect(() => {const handler = setTimeout(() => setDebounced(value), delay);return () => clearTimeout(handler);}, [value, delay]);return debounced;
    }function SearchComponent() {const [query, setQuery] = useState('');const debouncedQuery = useDebounce(query, 300);useEffect(() => {if (debouncedQuery) {fetchData(debouncedQuery);}}, [debouncedQuery]);return <input value={query} onChange={e => setQuery(e.target.value)} />;
    }
  4. 组件边界要合理将大而全的组件拆成职责单一的小组件:

    // 错误示范:所有逻辑都堆在一个组件里
    function ProfilePage({ user, posts, comments }) {return (<div><img src={user.avatar} alt="" /><h1>{user.name}</h1>{/* ... posts 和 comments 也都在这里渲染 ... */}</div>);
    }// 优化后:拆分成多个子组件
    function ProfilePage({ user, posts, comments }) {return (<><ProfileHeader user={user} /><UserPosts posts={posts} /><UserComments comments={comments} /></>);
    }
  5. 代码分割 + 懒加载对重量级组件动态加载,首屏加载更快:

    import React, { Suspense, lazy } from 'react';const HeavyComponent = lazy(() => import('./HeavyComponent'));function App() {return (<Suspense fallback={<div>Loading...</div>}><HeavyComponent /></Suspense>);
    }
  6. 避免匿名函数出现在 JSX 中匿名函数每次渲染都会新建,导致子组件无谓重渲染:

    // 错误示范:每次渲染都会创建新的函数引用
    <button onClick={() => handleSubmit(id)}>Submit</button>// 优化后:用 useCallback 保持函数引用稳定
    import { useCallback } from 'react';function SubmitButton({ id, handleSubmit }) {const onClick = useCallback(() => handleSubmit(id), [handleSubmit, id]);return <button onClick={onClick}>Submit</button>;
    }
  7. 使用 use-context-selector 精准订阅 Context只在真正使用的数据变化时触发重渲染:

    import React from 'react';
    import { createContext, useContextSelector } from 'use-context-selector';const MyContext = createContext({ count: 0, user: {} });function Counter() {// 只有 count 改变时才会重新渲染const count = useContextSelector(MyContext, ctx => ctx.count);return <div>Count: {count}</div>;
    }

将这些实战锦囊逐条落地,你的 React 应用性能将从“修修补补”一跃到“结构先行”,让优化变得水到渠成。


精英级思维:以数据流为核心

真正顶尖的 React 工程师,先思考「数据怎么流」,再设计组件。当数据来源、使用频率、目的地都理清后,组件结构和状态层次自然而然地对性能友好。


从“打怪”到“布局”——五步行动计划

  1. 现状体检:Profile → 找出高频渲染与滥用 Context/Prop Drilling。

  2. 重构组件树:拆解垂直职责,状态放最近公共祖先。

  3. 精准 Memo 化:先测再优化,去掉无效 memo。

  4. 细粒度选择:改用自定义 Selector 钩子,仅订阅必要数据。

  5. 持续 Profile:每次改动后都要测,关注用户可感知的卡顿。


真相大白

性能不是临时加上的“辣椒粉”,而是从架构层面烹饪出来的佳肴。顶级工程师要做的,不是盲目贴HOOK,而是从一开始就让渲染“刚刚好”,让每一次更新都精准命中目标组件。

若仍在重渲染的陷阱里苦苦挣扎,不妨换个思路:数据流——组件边界——状态层次。掌握这三步,性能问题便无处藏身。

前端AI·探索:涵盖动效、React Hooks、Vue 技巧、LLM 应用、Python 脚本等专栏,案例驱动实战学习,点击原文了解更多详情。

图片

最后:

python 技巧精讲

React Hook 深入浅出

CSS技巧与案例详解

vue2与vue3技巧合集

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

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

相关文章

流水线的安全与合规 - 构建可信的交付链

流水线的安全与合规 - 构建可信的交付链 “安全左移 (Shift-Left Security)”的理念 “安全左移”是 DevSecOps 的核心理念,指的是将安全测试和考量,从软件开发生命周期 (SDLC) 的末端(发布前),尽可能地向左移动到更早的阶段(如编码、构建、测试阶段)。 为何对 SRE 至…

​​​​​​​神经网络基础讲解 一

​​一.神经网络 ​ ​​1. 全连接神经网络&#xff08;Fully Connected Network, FCN&#xff09;​​ ​​核心概念&#xff1a;​​ ​​输入层​​&#xff1a;接收原始数据&#xff08;如数字、图片像素等&#xff09; 数字矩阵 。​​隐藏层​​&#xff1a;对数据…

MySQL 8.0 OCP 英文题库解析(二十二)

Oracle 为庆祝 MySQL 30 周年&#xff0c;截止到 2025.07.31 之前。所有人均可以免费考取原价245美元的MySQL OCP 认证。 从今天开始&#xff0c;将英文题库免费公布出来&#xff0c;并进行解析&#xff0c;帮助大家在一个月之内轻松通过OCP认证。 本期公布试题201~210 试题2…

【大模型推理】PD分离场景下decoder负载均衡,如何选取decoder

https://mp.weixin.qq.com/s?__bizMzg4NTczNzg2OA&mid2247507420&idx1&sn4b32726abd205c7f94144bcb9105330f&chksmce64b9fc7f1d8de04a40b0153302dee52262c6f104c67195e2586e75c8093b8be493f252c8a3#rd 在非 Local 场景下&#xff0c;Prefill 定时获取 Decode …

【IP地址】IP应用场景的使用方向

网络安全领域 通过IP地址查询&#xff0c;安全系统能够实时监控网络流量&#xff0c;识别异常访问行为。例如&#xff0c;当某个IP地址在短时间内频繁发起大量请求&#xff0c;且访问模式与正常用户存在明显差异时&#xff0c;系统可将其标记为可疑IP&#xff0c;触发风险预警…

3-18 WPS JS宏 颜色设置实例应用(按条件设置单元格颜色)学习笔记

前面讲解了关于单元格的一些格式的设置&#xff0c;本节课再讲解一下各种清除方法。 1.函数解析与用法 Range().clear()//清除全部 Range().Value2null //清除内容 Range().ClearContents()//清除内容 Range().ClearFormats()//清除格式 Range().EntireRow.Range()//以Ra…

从零开始的云计算生活——第二十天,脚踏实地,SSH与Rsync服务

目录 一.故事背景 二.SSH带外管理 1.概述 2. 配置文件 3.命令解析 4.登录方式配置 a.用户名密码登录 b.公钥验证登录 5.实操生成密钥对 三.Rsyncsersync实现数据实时同步 1.rsync概述 2.rsync运行原理 3.rsync部署 4.备份测试 配置备份目录 5.rsyncsersync 实现…

SpringAI + DeepSeek大模型应用开发 - 初识篇

一、认识AI 1. AI的发展 AI&#xff0c;人工智能&#xff08;Artificial Intelligence&#xff09;&#xff0c;使机器能像人类一样思考、学习和解决问题的技术。 2. 大模型及其原理 在自然语言处理&#xff08;Natural Language Processing, NLP&#xff09;中&#xff0c;…

c++第八天-多态

虚函数虚析构函数纯虚函数与抽象类 多态实现的条件&#xff1a;&#xff08;1&#xff09;公有继承 &#xff08;2&#xff09;派生类重写基类虚函数 &#xff08;3&#xff09;基类指针/引用指向派生类对象 虚函数不能是构造函数&#xff0c;不能是静态函数&#xff0c;不能…

全景图渲染Vue3+TS使用Photo Sphere Viewer插件实现

1.Photo Sphere Viewer插件安装: title=插件安装 体验AI代码助手 代码解读复制代码npm install photo-sphere-viewer -S 或 yarn add photo-sphere-viewer -S 2.原始全景图展示 初始化标签容器 体验AI代码助手 代码解读复制代码 // 全景图的根节点必须要具备宽高 TS引用,创建…

Redis之分布式锁(3)

这篇文章我们来详细介绍一下如何正确地基于Redis实现分布式锁。 基于Redis的分布式锁实现 组件依赖 首先通过Maven引入Jedis开源组件&#xff0c;在pom.xml文件加入下面的代码&#xff1a; <dependency><groupId>redis.clients</groupId><artifactId&g…

Java课堂笔记11

三个修饰符 一、abstract&#xff08;抽象&#xff09; 1.抽象方法只能在抽象的类里&#xff0c;只有方法的声明&#xff0c;没有方法的实现。&#xff08;没有{}直接&#xff1b;结尾&#xff09;。 2.abstract修饰的类称为抽象类。 注意&#xff1a;&#xff08;1&#x…

Linux 核心知识点整理(高频考点版)

一、编译与工具链 GCC 编译流程 四阶段&#xff1a;预处理&#xff08;-E&#xff0c;处理头文件 / 宏&#xff09;→ 编译&#xff08;-S&#xff0c;生成汇编&#xff09;→ 汇编&#xff08;-c&#xff0c;生成目标文件&#xff09;→ 链接&#xff08;生成可执行程序&…

轻量化社交管理方案:Skout与云手机的巧妙搭配

在移动社交时代&#xff0c;许多用户开始尝试通过多账号管理来拓展社交圈层。近期测试了Skout社交平台与亚矩阵云手机的搭配使用&#xff0c;发现这个组合为轻量级社交账号管理提供了一个值得关注的解决方案。 基础功能介绍 这套组合的核心优势在于&#xff1a; 通过云手机实…

ETL连接器好用吗?如何实现ETL连接?

目录 一、ETL连接器的功能和优势 1. 数据抽取能力 2. 数据转换功能 3. 数据加载功能 4. 优势总结 二、实现ETL连接的步骤 1. 需求分析 2. 选择合适的ETL连接器 3. 配置数据源和目标系统 4. 设计ETL流程 5. 开发和测试ETL任务 6. 部署和监控ETL任务 三、ETL连接器在…

uniapp实现聊天中的接发消息自动滚动、消息定位和回到底部

前言 前言无需多言&#xff0c;想必大家对聊天软件的功能已经很熟悉&#xff0c; 这里不做过多赘述&#xff0c;笔者通过uniapp实现聊天中的接发消息自动滚动、消息定位和回到底部。 代码实现 <template><view class"chat-container"><!-- 消息列表…

MyBatisMyBatis plus

整合 MyBatis 到 Spring 或 Spring Boot 项目中&#xff0c;可以极大地简化开发流程&#xff0c;尤其是当使用 Spring Boot 时&#xff0c;它提供了自动配置功能&#xff0c;使得集成更加简便。 在 Spring Boot 中整合 MyBatis 1. 添加依赖 首先&#xff0c;在 pom.xml 文件中…

Stable Diffusion 实战-手机壁纸制作 第二篇:优化那些“崩脸”和“马赛克”问题,让图像更加完美!

欢迎回来!在《StableDiffusion实战-手机壁纸制作》系列的第一篇中,我们成功完成了基础操作,制作出了令人炫目的手机壁纸。 今天,我们将进入一个更高阶的领域——优化处理。因为谁不想让生成的艺术品更完美呢?尤其是避免“崩脸”和“马赛克”这种让人抓狂的问题! 创作的路…

408第一季 - 数据结构 - B树与B+树

B树 性质 可以看见一个节点可以有多个数字了 然后也满足左小右大的特征 然后所有的叶子节点都在同一层&#xff0c;然后2个数字的节点就可以有3个分支 然后呢&#xff0c;每个节点里面到底有几个数字是有规定的公式的 就这个公式&#xff0c;m是5阶的&#xff0c;算出来是2和…

SSRF5 Gopher 协议对内网 Web 服务进行 sql 注入 GET 类型和POST类型

实验环境&#xff1b; Centos7.6上同时安装sqli-lib和pikachu 一.Gopher 协议对内网 Web 服务进行 sql 注入 GET 类型 我们先访问sqli-lib第1关 然后我们构造URL&#xff1a; http://192.168.112.12/pikachu-master/vul/ssrf/ssrf_curl.php?urlhttp://192.168.112.12/sql…