Vue渲染对比实验
匿名 props 触发子组件渲染的对比结果
本文记录一次在 Vue 3 环境中针对“父组件传递匿名 props 是否导致子组件重新渲染”的实测结论,分别覆盖模板 (SFC) 与 TSX 写法。
实验现象概览
| 场景 | 匿名函数 | 匿名数组 | 匿名对象 |
|---|---|---|---|
| Vue SFC 模板语法 | 触发 | 未触发 | 未触发 |
| Vue TSX | 触发 | 触发 | 触发 |
触发判断方式:子组件内部维护渲染计数与 props 引用变更计数,每次父组件更新后查看计数变化。
关键结论
1. SFC 模板语法
- 模板中的字面量数组、字面量对象会被 Vue 编译阶段提升为常量引用,因此并不会在每次渲染时创建新引用。
- 匿名函数会在每轮渲染时重新创建,导致子组件收到新函数引用而更新。
vue
<template>
<ChildProbe
label="匿名 props"
:fn="() => counter"
:items="['a', 'b', 'c']" <!-- 字面量数组被静态提升 -->
:config="{ role: 'inline' }" <!-- 字面量对象被静态提升 -->
/>
<ChildProbe
label="缓存 props"
:fn="stableFn"
:items="stableItems"
:config="stableConfig"
/>
</template>
<script setup lang="ts">
const counter = ref(0)
const stableFn = () => counter.value
const stableItems = ['a', 'b', 'c']
const stableConfig = { role: 'stable' }
</script>
2. TSX 写法
- TSX 中的字面量不会自动被静态提升,每次渲染都会重新生成函数、数组和对象的新引用。
- 因此,匿名传参的三种类型都会触发子组件的
props改变与重新渲染。 - 通过在
setup中提前声明并使用同一引用,可以消除多余渲染。
tsx
export default defineComponent({
setup() {
const counter = ref(0)
const stableFn = () => counter.value
const stableItems = ['a', 'b', 'c']
const stableConfig = { role: 'stable' }
return () => (
<>
<TsxChildProbe
label="匿名 props"
fn={() => counter.value}
items={['a', 'b', 'c']}
config={{ role: 'inline' }}
/>
<TsxChildProbe
label="缓存 props"
fn={stableFn}
items={stableItems}
config={stableConfig}
/>
</>
)
},
})
3. 子组件如何统计
ts
export function useRenderProbe(props) {
const renderCount = ref(1)
const referenceChanges = reactive({ fn: 0, items: 0, config: 0 })
onUpdated(() => {
renderCount.value += 1
})
watch(
() => props.fn,
(next, prev) => {
if (next !== prev) referenceChanges.fn += 1
}
)
// items、config 同理统计
}
建议
- SFC 模板:仅需关注匿名函数,数组/对象字面量保持不变时可视为稳定引用。
- TSX:若要避免额外渲染,应在
setup中提前声明所有需要传入的函数、数组与对象,保证引用稳定。
![[微笑]](/face/0.gif)
![[嘻嘻]](/face/1.gif)
![[哈哈]](/face/2.gif)
![[可爱]](/face/3.gif)
![[可怜]](/face/4.gif)
![[挖鼻]](/face/5.gif)
![[吃惊]](/face/6.gif)
![[害羞]](/face/7.gif)
![[挤眼]](/face/8.gif)
![[闭嘴]](/face/9.gif)
![[鄙视]](/face/10.gif)
![[爱你]](/face/11.gif)
![[泪]](/face/12.gif)
![[偷笑]](/face/13.gif)
![[亲亲]](/face/14.gif)
![[生病]](/face/15.gif)
![[太开心]](/face/16.gif)
![[白眼]](/face/17.gif)
![[右哼哼]](/face/18.gif)
![[左哼哼]](/face/19.gif)
![[嘘]](/face/20.gif)
![[衰]](/face/21.gif)
![[委屈]](/face/22.gif)
![[吐]](/face/23.gif)
![[哈欠]](/face/24.gif)
![[抱抱]](/face/25.gif)
![[怒]](/face/26.gif)
![[疑问]](/face/27.gif)
![[馋嘴]](/face/28.gif)
![[拜拜]](/face/29.gif)
![[思考]](/face/30.gif)
![[汗]](/face/31.gif)
![[困]](/face/32.gif)
![[睡]](/face/33.gif)
![[钱]](/face/34.gif)
![[失望]](/face/35.gif)
![[酷]](/face/36.gif)
![[色]](/face/37.gif)
![[哼]](/face/38.gif)
![[鼓掌]](/face/39.gif)
![[晕]](/face/40.gif)
![[悲伤]](/face/41.gif)
![[抓狂]](/face/42.gif)
![[黑线]](/face/43.gif)
![[阴险]](/face/44.gif)
![[怒骂]](/face/45.gif)
![[互粉]](/face/46.gif)
![[心]](/face/47.gif)
![[伤心]](/face/48.gif)
![[猪头]](/face/49.gif)
![[熊猫]](/face/50.gif)
![[兔子]](/face/51.gif)
![[ok]](/face/52.gif)
![[耶]](/face/53.gif)
![[good]](/face/54.gif)
![[NO]](/face/55.gif)
![[赞]](/face/56.gif)
![[来]](/face/57.gif)
![[弱]](/face/58.gif)
![[草泥马]](/face/59.gif)
![[神马]](/face/60.gif)
![[囧]](/face/61.gif)
![[浮云]](/face/62.gif)
![[给力]](/face/63.gif)
![[围观]](/face/64.gif)
![[威武]](/face/65.gif)
![[奥特曼]](/face/66.gif)
![[礼物]](/face/67.gif)
![[钟]](/face/68.gif)
![[话筒]](/face/69.gif)
![[蜡烛]](/face/70.gif)
![[蛋糕]](/face/71.gif)