Pixiv - KiraraShss
714 字
4 分钟
编译期宏和作用域插槽
欢迎来到我的博客!
使用 Markdown 格式编写。
今天学到了什么
- 编译期宏
- 作用域插槽
编译期宏
在 <script setup> 里有两类东西:
- 普通 JS 运行时代码:比如
console.log(...)、useAttrs()的调用(它会拿到一个运行时的代理对象)。 - 编译期宏(compile-time macro):
defineProps()/defineEmits()/defineExpose()这类。它们看起来像函数调用,但实际上在构建/编译 SFC 时就被“抽走”了,用来生成组件的props/emits配置;并不是等到运行时才“执行”这行再决定分流。
所以你以为的线性过程是:
- 先
useAttrs()→ attrs 里有 title - 再
defineProps(['title'])→ “改掉”attrs,让 title 消失
但真实过程更接近:
- 编译阶段:编译器先扫描整个
<script setup>,看到你声明了props: { title: ... }(由defineProps得到)。 - 运行阶段(组件实例创建/更新):Vue 拿到父组件传入的所有绑定后,按“已声明为 props/emits 的归类规则”一次性分流:
title既然是 prop → 进props- 其余未声明的 → 进
attrs
useAttrs()只是给你一个指向当前实例attrs的响应式只读代理,它反映的是“分流后的结果”。
另外还有个很常见的“误会来源”:
console.log(attrs)打印的是一个代理对象,很多时候控制台是延迟展开/显示的;你看到的可能是“后来展开时的最新状态”,不一定是 log 那一刻的快照。- 想看“当时那一刻”的值,用:
console.log({ ...attrs })或console.log(JSON.parse(JSON.stringify(attrs)))(后者不推荐常用,只是为了观察)。
- 想看“当时那一刻”的值,用:
如果你愿意,我可以直接看你当前的组件结构(父组件怎么传 title、子组件怎么接),然后给你改成一个最小可观测例子:用 watchEffect 同时输出 props.title 和 {...attrs},你会直观看到分流规则在起作用。
作用域插槽
不是为了“绕一圈”,而是为了把两件事拆开:数据归谁管,页面长什么样归谁管。
作用域插槽的本质是:子组件把自己手里的上下文数据暴露出来,父组件来决定怎么渲染。看起来像“传回父组件”,其实不是把数据回传,而是父组件写的模板,拿到了子组件提供的参数,在子组件的位置被渲染出来。
所以要分情况看:
- 如果数据本来就在父组件,而且父组件自己也能直接渲染,那就没必要再传给子组件再绕回来。
- 如果子组件负责列表、状态、加载逻辑,父组件负责外观,那作用域插槽就很合适。
- 它解决的是“子组件掌控数据,父组件掌控结构”的组合问题,不是为了增加传递层级。
赞助支持
如果这篇文章对你有帮助,欢迎赞助支持!
最后更新于 2026-04-21,距今已过 133 天
部分内容可能已过时