我在开发一个数据展示页面时,遇到了性能问题。用户反馈页面在操作时感觉很卡。通过性能分析工具,我发现某个子组件在父组件更新时频繁重新渲染,这显然是不必要的。
作为react开发者,我首先想到了用React.memo来优化性能。但实际使用中,却发现它没有达到预期效果。
这是我的代码:
const Child = React.memo(({ data }) => {
console.log("子组件重新渲染了");
return (
<div>
<h3>{data.label}</h3>
<p>{data.description}</p>
</div>
);
});
function App() {
const [count, setCount] = useState(0);
const data = { label: "用户信息" };
return (
<div>
<button onClick={() => setCount(count + 1)}>
点击次数: {count}
</button>
<Child data={data} />
</div>
);
}
我原本的想法是:
点击按钮时,count状态更新
子组件的props看起来没有变化
React.memo应该阻止子组件重新渲染
控制台不应该输出重新渲染的信息
但实际情况是:
每次点击按钮
控制台都会输出"子组件重新渲染了"
React.memo好像完全没有作用
看到这个问题,我开始从几个方面思考:
首先,React.memo是如何工作的?它通过比较props的前后值来决定是否重新渲染组件。但它是怎么比较的呢?
其次,在JavaScript中,对象是怎么比较的?两个看起来一样的对象,它们真的相等吗?
最后,每次组件重新渲染时,函数内部的变量会发生什么变化?
为了验证我的想法,我做了一个小实验:
// 测试对象比较
const obj1 = { label: "测试" };
const obj2 = { label: "测试" };
console.log(obj1 === obj2); // 结果是false
// 模拟组件渲染
function testRender() {
const data = { label: "测试" };
return data;
}
const result1 = testRender();
const result2 = testRender();
console.log(result1 === result2); // 结果还是false
这个实验让我明白了问题的关键。
现在来看这个选择题,哪个选项是正确的?
A. React.memo不能处理对象类型的props,只能处理基本类型
B. data对象在每次渲染时都被重新创建,引用地址改变,导致React.memo认为props发生了变化
C. count状态更新会强制所有子组件重新渲染,React.memo无效
D. console.log语句导致组件每次都会执行
正确答案是B。
解释一下:每次App组件重新渲染时,const data = { label: "用户信息" }这行代码都会执行,创建一个全新的对象。虽然对象的内容相同,但它们在内存中的地址不同。React.memo使用浅比较,发现前后两个data对象的引用不同,就认为props发生了变化,于是重新渲染组件。
这种问题在实际开发中经常遇到:
场景1:列表渲染
function TodoList() {
const [todos, setTodos] = useState([]);
return todos.map(todo => (
<TodoItem
key={todo.id}
todo={todo}
onDelete={() => handleDelete(todo.id)} // 每次都会创建新函数
/>
));
}
场景2:配置传递
function Dashboard() {
const [refresh, setRefresh] = useState(0);
const chartConfig = { // 每次渲染都会创建新对象
width: 800,
height: 400
};
return <Chart config={chartConfig} />;
}
场景3:内联样式
function Card({ title }) {
const style = { // 每次渲染都会创建新对象
padding: '20px',
borderRadius: '8px'
};
return <div style={style}>{title}</div>;
}
知道了问题原因,我们可以这样优化:
使用useMemo缓存对象
function App() {
const [count, setCount] = useState(0);
const data = useMemo(() => ({
label: "用户信息"
}), []); // 依赖数组为空,只在初始化时创建一次
return (
<div>
<button onClick={() => setCount(count + 1)}>
点击次数: {count}
</button>
<Child data={data} />
</div>
);
}
将对象移到组件外部
const staticData = { label: "用户信息" };
function App() {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount(count + 1)}>
点击次数: {count}
</button>
<Child data={staticData} />
</div>
);
}
使用自定义比较函数
const Child = React.memo(({ data }) => {
console.log("子组件重新渲染了");
return <div>{data.label}</div>;
}, (prevProps, nextProps) => {
// 自定义比较逻辑
return prevProps.data.label === nextProps.data.label;
});
你可以在本地运行这段代码来验证:
import React, { useState, memo } from 'react';
const Child = memo(({ data }) => {
console.log('子组件渲染了, 对象引用:', data);
return <div>{data.label}</div>;
});
function App() {
const [count, setCount] = useState(0);
const data = { label: "测试" };
console.log('父组件渲染了, 对象引用:', data);
return (
<>
<button onClick={() => setCount(count + 1)}>
点击: {count}
</button>
<Child data={data} />
</>
);
}
观察控制台输出的对象引用,你会发现每次渲染时data对象的引用都在变化。
React.memo失效的根本原因是JavaScript的对象引用特性。要正确使用React.memo,需要注意:
避免在组件内部创建新的对象、数组或函数
使用useMemo、useCallback来缓存引用类型
对于不会变化的数据,可以移到组件外部
在必要时使用自定义比较函数
理解这些原理后,你就能更好地使用React.memo来优化应用性能,避免不必要的重新渲染。
本文内容仅供个人学习/研究/参考使用,不构成任何决策建议或专业指导。分享/转载时请标明原文来源,同时请勿将内容用于商业售卖、虚假宣传等非学习用途哦~感谢您的理解与支持!
为解决JS加载速度慢,采用js的延时加载,和动态加载。由于js的堵塞特性,当浏览器在加载javascript代码时,不能同时做其他任何事情,如果javascript执行时间越久,浏览器等待响应的时间就越久。
如何提高CSS性能,根据页面的加载性能和CSS代码性能,主要表现为: 加载性能 (主要是从减少文件体积,减少阻塞加载,提高并发方面入手),选择器性能,渲染性能,可维护性。
css的加载是不会阻塞DOM的解析,但是会阻塞DOM的渲染,会阻塞link后面js语句的执行。这是由于浏览器为了防止html页面的重复渲染而降低性能,所以浏览器只会在加载的时候去解析dom树,然后等在css加载完成之后才进行dom的渲染以及执行后面的js语句。
性能十分重要。然而,我们真的知道性能瓶颈具体在哪儿吗?是执行复杂的 JavaScript,下载缓慢的 Web 字体,巨大的图片,还是卡顿的渲染?研究摇树(Tree Shaking),作用域提升(Scope Hoisting)
Js高性能总结:加载和运行、数据访问、DOM编程、算法和流程控制、响应接口、Ajax 异步JavaScript和XML、编程实践...
前端网站性能优化规则:网络加载类、页面渲染类。包括:减少 HTTP 资源请求次数、减小 HTTP 请求大小、避免页面中空的 href 和 src、合理设置 Etag 和 Last-Modified、使用可缓存的 AJAX、减少 DOM 元素数量和深度等
性能一直以来是前端开发中非常重要的话题。随着前端能做的事情越来越多,浏览器能力被无限放大和利用:从 web 游戏到复杂单页面应用,从 NodeJS 服务到 web VR/AR 和数据可视化,前端工程师总是在突破极限
BigPipe是一个重新设计的基础动态网页服务体系。大体思路是,分解网页成叫做Pagelets的小块,然后通过Web服务器和浏览器建立管道并管理他们在不同阶段的运行。这是类似于大多数现代微处理器的流水线执行过程:多重指令管线通过不同的处理器执行单元,以达到性能的最佳。
你知道我们可以在浏览器中用css开启硬件加速,使GPU (Graphics Processing Unit) 发挥功能,从而提升性能吗?现在大多数电脑的显卡都支持硬件加速。鉴于此,我们可以发挥GPU的力量,从而使我们的网站或应用表现的更为流畅。
像淘宝网站等,页面中有着大量图片,一次性全部加载这些图片会使浏览器发送大量请求和造成浪费。采用懒加载技术,即用户浏览到哪儿,就加载该处的图片。这样节省网络资源、提升用户体验、减少服务器压力。
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!