React 中 props 身份与重渲染
React 中对象、数组、函数等 props 引用变化如何影响子组件重渲染和 memo 生效。
#tech / dev / frame
#resource / react
#type / synthesis
#status / growing
[!info] related notes
- 所属 MOC: React MOC
- 相关概念: React组件渲染与重渲染, react-memo, react-use-memo, react-use-callback, ECMAScript原始值与引用值
- 面试问法: react-prop-identity-interview-question
React 中 props 身份与重渲染
React 性能问题里一个非常高频的坑,是“值看起来没变,但引用其实变了”。
一句话定义
当对象、数组、函数作为 props 传递时,即使内容看起来一样,只要引用是新的,子组件和 React.memo 就可能把它当成变化。
为什么这件事重要
- 父组件每次执行,常常都会创建新的对象和函数
- 子组件如果依赖引用稳定性,就可能无意义重渲染
React.memo是否真正生效,常常就卡在这里
底层直觉其实来自 JavaScript 的值语义
- 原始值更接近“按值比较”
- 对象、数组、函数更接近“按身份比较”
所以:
Object.is(1, 1) // true
Object.is('a', 'a') // true
Object.is({}, {}) // false
Object.is([], []) // false
React 讨论“props 变了没有”时,很多时候不是在做深比较,而是在利用这种身份差异快速判断。
最常见的几种情况
对象 props
<Child config={{ size: 'small' }} />
每次父组件执行时,这个对象都是新引用。
数组 props
<Child list={[1, 2, 3]} />
同样会不断生成新引用。
函数 props
<Child onClick={() => doSomething()} />
函数身份也会在每次渲染时变化。
怎么看待这个问题更合理
- 不要一看到新引用就紧张
- 只有子组件确实重、渲染频繁、且优化链路依赖稳定引用时,才值得处理
最短记忆方式
React 不只看“值像不像”,很多时候也会被“引用是不是同一个”影响。
面试要点
来自 react-prop-identity-interview-question 的面试视角整理。
一句话回答
因为对象、数组、函数在父组件每次重新执行时,常常都会生成新的引用;即使内容看起来一样,React.memo 也可能把它当成 props 已变化。
最稳的回答主线
- 基本类型更容易保持稳定比较
- 对象、数组、函数经常在每次渲染时变成新引用
- 如果子组件优化链路依赖引用稳定,就可能需要
useMemo或useCallback
一个更完整的面试表达
可以这样答:
React 里对象、数组、函数作为 props 时,问题不在“内容看起来一样”,而在“是不是同一个引用”。父组件每次重新执行时,内联对象和内联函数通常都会生成新引用,所以
React.memo很可能把它们当成 props 已变化,导致子组件没法跳过重渲染。
一个典型例子
<Child config={{ size: 'small' }} />
<Child onClick={() => doSomething(id)} />
这两种写法在父组件每次渲染时,都会生成新引用。
一个加分点
如果面试官继续问底层原因,可以补一句:
这其实不是 React 特有问题,而是 JavaScript 的值语义决定的。原始值更接近按值比较,对象和函数更接近按身份比较。
什么时候才值得优化
不是一看到对象 props 就要 useMemo / useCallback。
只有当:
- 子组件真的比较重
- 子组件已经用了
React.memo - 引用稳定性能换来实际收益
这时再优化才合理。
最短记忆方式
内容像一样,不代表引用还是同一个。