React.memo为什么没有用?一个真实的性能优化问题

更新日期: 2025-10-15 阅读: 25 标签: 性能

我在开发一个数据展示页面时,遇到了性能问题。用户反馈页面在操作时感觉很卡。通过性能分析工具,我发现某个子组件在父组件更新时频繁重新渲染,这显然是不必要的。

作为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>;
}


解决方案

知道了问题原因,我们可以这样优化:

  1. 使用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>
  );
}
  1. 将对象移到组件外部

const staticData = { label: "用户信息" };

function App() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>
        点击次数: {count}
      </button>
      <Child data={staticData} />
    </div>
  );
}
  1. 使用自定义比较函数

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,需要注意:

  1. 避免在组件内部创建新的对象、数组或函数

  2. 使用useMemo、useCallback来缓存引用类型

  3. 对于不会变化的数据,可以移到组件外部

  4. 在必要时使用自定义比较函数

理解这些原理后,你就能更好地使用React.memo来优化应用性能,避免不必要的重新渲染。

本文内容仅供个人学习/研究/参考使用,不构成任何决策建议或专业指导。分享/转载时请标明原文来源,同时请勿将内容用于商业售卖、虚假宣传等非学习用途哦~感谢您的理解与支持!

链接: https://fly63.com/article/detial/13012

提高js加载速度,实现js无阻塞加载方式,高性能的加载执行JavaScript

为解决JS加载速度慢,采用js的延时加载,和动态加载。由于js的堵塞特性,当浏览器在加载javascript代码时,不能同时做其他任何事情,如果javascript执行时间越久,浏览器等待响应的时间就越久。

如何提高CSS性能?CSS优化、提高性能提升总汇

如何提高CSS性能,根据页面的加载性能和CSS代码性能,主要表现为: 加载性能 (主要是从减少文件体积,减少阻塞加载,提高并发方面入手),选择器性能,渲染性能,可维护性。

前端性能优化_css加载会造成哪些阻塞现象?

css的加载是不会阻塞DOM的解析,但是会阻塞DOM的渲染,会阻塞link后面js语句的执行。这是由于浏览器为了防止html页面的重复渲染而降低性能,所以浏览器只会在加载的时候去解析dom树,然后等在css加载完成之后才进行dom的渲染以及执行后面的js语句。

2018 前端性能检查表

性能十分重要。然而,我们真的知道性能瓶颈具体在哪儿吗?是执行复杂的 JavaScript,下载缓慢的 Web 字体,巨大的图片,还是卡顿的渲染?研究摇树(Tree Shaking),作用域提升(Scope Hoisting)

高性能Javascript总结

Js高性能总结:加载和运行、数据访问、DOM编程、算法和流程控制、响应接口、Ajax 异步JavaScript和XML、编程实践...

优化网站性能规则_前端性能优化策略【网络加载、页面渲染】

前端网站性能优化规则:网络加载类、页面渲染类。包括:减少 HTTP 资源请求次数、减小 HTTP 请求大小、避免页面中空的 href 和 src、合理设置 Etag 和 Last-Modified、使用可缓存的 AJAX、减少 DOM 元素数量和深度等

前端性能的本质是什么?

性能一直以来是前端开发中非常重要的话题。随着前端能做的事情越来越多,浏览器能力被无限放大和利用:从 web 游戏到复杂单页面应用,从 NodeJS 服务到 web VR/AR 和数据可视化,前端工程师总是在突破极限

BigPipe_高性能流水线页面技术

BigPipe是一个重新设计的基础动态网页服务体系。大体思路是,分解网页成叫做Pagelets的小块,然后通过Web服务器和浏览器建立管道并管理他们在不同阶段的运行。这是类似于大多数现代微处理器的流水线执行过程:多重指令管线通过不同的处理器执行单元,以达到性能的最佳。

用CSS开启硬件加速来提高网站性能

你知道我们可以在浏览器中用css开启硬件加速,使GPU (Graphics Processing Unit) 发挥功能,从而提升性能吗?现在大多数电脑的显卡都支持硬件加速。鉴于此,我们可以发挥GPU的力量,从而使我们的网站或应用表现的更为流畅。

原生js实现懒加载并节流

像淘宝网站等,页面中有着大量图片,一次性全部加载这些图片会使浏览器发送大量请求和造成浪费。采用懒加载技术,即用户浏览到哪儿,就加载该处的图片。这样节省网络资源、提升用户体验、减少服务器压力。

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!