对象展开运算符性能影响取决于被展开对象特性而非语法本身:大数据集触发响应式代理或深度diff、函数引用破坏缓存、类型宽泛拖慢TS检查;应按需拆分、解构传值、避免全量展开第三方组件或SSR场景。

对象展开运算符({...props})在大规模组件 Props 传递中,表面看是语法糖,实则对性能有可观测的影响——它不增加逻辑负担,但会放大底层机制的开销:响应式代理创建、属性遍历深度、引用拷贝粒度、以及 TypeScript 类型检查路径增长。关键不在“用不用”,而在“传什么、谁接收、是否必要”。
一、展开运算符本身不耗性能,但被展开的对象决定成本
JS 展开操作是浅层遍历,时间复杂度 O(n),n 是对象自有可枚举属性数。若 commonProps 只有 5 个字符串/数字字段,开销可忽略;但若它包含:
- 一个未冻结的万级数组(如
items: [...10000])→ Vue 会对其每个元素尝试reactive(),React 会触发深度 props diff - 一个深层嵌套对象(如
config: { ui: { theme: { colors: { primary: {...} } } } })→ React.memo 或 Vue 的依赖追踪会递归收集,内存驻留上升 - 函数引用未绑定或含闭包(如
onItemClick: (item) => console.log(self.state, item))→ 每次渲染都生成新函数,破坏React.memo或shouldComponentUpdate缓存
二、Vue 与 React 中的差异表现
同一种写法,在不同框架下性能归因不同:
-
Vue(Composition API):展开后传入 setup() 的 props 对象,若含大数据,会触发
reactive()自动代理 → 建议对只读大数据提前markRaw(),再展开;否则子组件哪怕只读props.items.length,也会拖慢整个响应式系统 -
React(函数组件):展开不会自动 deep-clone,但若父组件用
useMemo(() => ({...}), [])包裹 props 对象,再展开,能避免子组件因 props 引用变化而误更新;反之,若每次渲染都新建对象再展开({...{a:1,b:2}}),React.memo失效
三、可落地的轻量优化策略
不改架构,仅调整展开方式,就能缓解多数场景压力:
- 把高频变动字段(如
loading、error)和静态数据(如config、options)拆成两个对象分别展开:<List {...staticProps} {...uiState} />,便于 memo 控制 - 用解构代替全量展开,只传子组件真正需要的字段:
<Item id={item.id} name={item.name} />,比<Item {...item} />更可控 - 对大型列表类子组件,禁止展开整个数据集;改用 ID 数组 + 上下文 fetch:
<ItemList ids={[1,2,3]} />,内部按需查缓存或请求 - TypeScript 项目中,确保展开对象类型收敛——避免
any或宽泛的Record<string, unknown>,否则类型检查会线性变慢,尤其在 monorepo 多组件共享 props 类型时
四、何时该放弃展开,改用更明确的传递方式
以下情况展开运算符已从“便利”变为“隐患”:
- 子组件是第三方库组件(如
Antd Table、Element Plus ElSelect),其内部对 props 做了大量非标准处理,展开易触发未预期的重渲染或校验逻辑 - 父子组件间存在跨层级状态联动(如表单联动),展开后丢失字段来源语义,调试时难以定位哪一层修改了哪个 key
- 需要做服务端渲染(SSR)或静态生成(SSG),展开对象若含客户端计算值(
window.xxx、Date.now()),会导致 hydration mismatch


















