避免不必要渲染: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 图表
  • 大型表单
  • 树形组件
相关推荐
aZhe的全栈知识分享1 天前
AI 还原 Figma 效果总达不到预期?先别急着让它写代码
前端·后端·面试
Fuyo_11191 天前
C++ 运行时多态与虚函数机制全面剖析【面试重点】
开发语言·c++·面试
IT规划师1 天前
一文讲透五种发布机制:滚动、蓝绿、金丝雀、红黑与 AB
后端·面试
ZhengEnCi2 天前
工科读研真的能抗学历贬值吗:研究生 vs 本科生的真实差距与选型判断
面试
終不似少年遊*2 天前
【安全业务Agent】从场景到编排
python·react.js·ai·langchain·agent·安全架构·工作流
Rain的Java大神之路2 天前
国家数据集团校招、社招都开了:总部雄安,这些技术岗值得认真看看
java·后端·面试
Moment2 天前
为什么这么多 Agent Harness 都选择 TypeScript
前端·后端·面试
时间的拾荒人2 天前
CMake 实战指南:从基础语法到静态库/动态库制作与链接
开发语言·c++·面试
JavaPub-rodert2 天前
从零搭建通用管理后台:ShiyuAdmin 的 Go、React 与 RBAC 实践
开发语言·react.js·golang
IT规划师2 天前
编程术语中的同步与异步:从"等不等"到工程取舍
后端·面试