避免不必要渲染:PureComponent、memo、useMemo、useCallback

这是 React 面试中高频考点,很多人会背概念,但面试官更喜欢问:

PureComponent、memo、useMemo、useCallback 有什么区别?什么时候用?为什么能减少渲染?

你可以这样理解:


React 渲染原理

父组件重新渲染时:

javascript 复制代码
function Parent() {
  const [count, setCount] = useState(0);

  return (
    <>
      <button onClick={() => setCount(count + 1)}>
        +
      </button>
      <Child />
    </>
  );
}

点击按钮后:

复制代码
Parent render
Child render

即使 Child 的 props 没变:

xml 复制代码
<Child />

React 默认仍会重新执行 Child 函数。

这就是性能浪费。


1. PureComponent

类组件专用

scala 复制代码
class Child extends React.PureComponent {
  render() {
    console.log("child render");
    return <div>Child</div>;
  }
}

相当于:

javascript 复制代码
shouldComponentUpdate(
  nextProps,
  nextState
){
  return false;
}

但实际上内部做的是:

vbnet 复制代码
浅比较(Shallow Compare)

比较:

ini 复制代码
oldProps === newProps
oldState === newState

相同:

复制代码
不更新

不同:

复制代码
重新渲染

2. React.memo

函数组件版本 PureComponent

javascript 复制代码
const Child = React.memo(() => {
  console.log("child render");

  return <div>Child</div>;
});

父组件:

javascript 复制代码
function Parent() {
  const [count, setCount] = useState(0);

  return (
    <>
      <button onClick={() => setCount(count + 1)}>
        +
      </button>

      <Child />
    </>
  );
}

点击按钮:

复制代码
Parent render

Child 不会重新渲染。


React.memo原理

内部也是:

scss 复制代码
shallowEqual(oldProps, newProps)

浅比较。


memo失效场景

ini 复制代码
<Child user={{ name: "Tom" }} />

每次 render:

diff 复制代码
{}
!== 
{}

引用变了。

所以:

复制代码
Child render
Child render
Child render

memo失效。


3. useMemo

缓存值

很多人误以为:

复制代码
useMemo 防止组件渲染

错。

它缓存的是:

复制代码
计算结果

例如

ini 复制代码
const total = useMemo(() => {
  return bigData.reduce(...)
}, [bigData]);

第一次:

复制代码
执行reduce

之后:

复制代码
直接返回缓存结果

不会重复计算。


不用 useMemo

ini 复制代码
const total = bigData.reduce(...)

Parent 每 render 一次:

复制代码
reduce执行一次

数据大时非常耗性能。


useMemo + memo

经典组合:

ini 复制代码
const user = useMemo(() => {
  return {
    name: "Tom"
  };
}, []);

<Child user={user}/>

这样:

sql 复制代码
user引用固定

memo 才能生效。


4. useCallback

缓存函数

很多人写:

ini 复制代码
<Child
  onClick={() => {
    console.log("click");
  }}
/>

每次 render:

javascript 复制代码
new Function()

都会生成新函数。


即使用了 memo:

ini 复制代码
const Child = memo(...)

也会重新渲染。

因为:

yaml 复制代码
oldFn !== newFn

解决:

ini 复制代码
const handleClick = useCallback(() => {
  console.log("click");
}, []);
ini 复制代码
<Child onClick={handleClick}/>

这样:

复制代码
函数引用固定

memo 生效。


useMemo vs useCallback

很多面试官喜欢问。

本质:

scss 复制代码
useCallback(fn,deps)

等于:

scss 复制代码
useMemo(() => fn, deps)

源码思想就是这样。


缓存值:

ini 复制代码
const user = useMemo(
  () => ({ name: "Tom" }),
  []
);

缓存函数:

ini 复制代码
const fn = useCallback(
  () => {},
  []
);

面试总结

API 作用 适用
PureComponent 类组件避免重复渲染 Class
React.memo 函数组件避免重复渲染 Function
useMemo 缓存计算结果 值
useCallback 缓存函数引用 函数

记住一句话:

复制代码
PureComponent / memo
解决「组件重复渲染」

useMemo
解决「重复计算」

useCallback
解决「函数引用变化导致的重复渲染」

实际项目中的最佳实践

在你做 React 管理后台(Ant Design、ECharts、大表格)时,最常见的优化组合是:

ini 复制代码
const columns = useMemo(() => [...], []);

const handleSearch = useCallback(() => {
  ...
}, []);

const TableList = memo(TableListComponent);

特别是:

  • Ant Design Table
  • ECharts 图表
  • 大型表单
  • 树形组件
相关推荐
晨米酱3 天前
AGENTS.md:Agent 的上下文策略层
面试·架构·agent
卡布鲁3 天前
把一个 Vite + Vue3 应用塞进 qiankun (React + Umi3) 主站:十个坑的复盘
前端·javascript·react.js
彧azz3 天前
Linux 环境下 Redis 学习总结:数据类型、持久化、锁、事务、主从与缓存问题
linux·redis·笔记·学习·面试
CoderYanger3 天前
A.每日一题:835. 图像重叠
java·开发语言·程序人生·leetcode·面试·职场和发展·学习方法
爱学习的执念3 天前
助力金九银十1000道软件测试面试题(功能、接口、自动化、WEB、APP.......)附答案
软件测试·面试
小的~~3 天前
银河麒麟V10 ARM部署TDengine(涛思)工业时序数据库安装与测试
面试·程序员创富
小刘在重生~3 天前
Oracle_day2 单行函数|组函数|分组|伪列|子查询|分页|多表连接
笔记·oracle·面试
用户298698530143 天前
在 React 中用 JavaScript 将 Word 转换为文本
前端·javascript·react.js
Sam_Deep_Thinking3 天前
关于java final关键字的可见性
java·后端·面试·程序员
Rain的Java大神之路4 天前
如何快速上传10G文件
java·spring boot·redis·后端·mysql·spring cloud·面试