Vue2、Vue3中的$scopedSlots和$slots区别小结
$scopedSlots(作用域插槽)
定义:子组件提供数据,父组件决定如何渲染,数据作用域属于子组件。
本质:子组件不直接渲染内容,而是接收一个函数,这个函数在子组件作用域内执行,从而让父组件的模板可以访问子组件的数据。
// 作用域插槽的本质:一个函数,子组件调用时传入数据
this.$scopedSlots.default = function(data) {
// 这个函数在父组件的作用域编译
// 但参数 data 来自子组件
return VNode // 返回渲染好的节点
}$slots(普通插槽)
定义:父组件提供内容,子组件决定在哪里渲染,数据作用域属于父组件。
本质:父组件在编译时就已经确定了插槽内容的所有数据和逻辑,子组件只是作为一个容器来摆放这些内容。
// 普通插槽的本质:父组件编译好的 VNode 数组 // 子组件只是被动接收 this.$slots.default = [VNode, VNode, ...] // 已经是渲染好的节点
数据作用域的指向
<!-- 父组件 Parent.vue -->
<template>
<div>
<!-- 普通插槽 -->
<ChildComponent>
<div>{{ parentMessage }}</div> <!-- ✅ 可以访问父组件数据 -->
<!-- <div>{{ childMessage }}</div> ❌ 不能访问子组件数据 -->
</ChildComponent>
<!-- 作用域插槽 -->
<ChildComponent>
<template v-slot:default="slotProps">
<div>{{ parentMessage }}</div> <!-- ✅ 可以访问父组件数据 -->
<div>{{ slotProps.childMessage }}</div> <!-- ✅ 可以访问子组件数据 -->
</template>
</ChildComponent>
</div>
</template>
<script>
export default {
data() {
return {
parentMessage: '父组件的数据' // 父组件作用域
}
}
}
</script><!-- 子组件 ChildComponent.vue -->
<template>
<div>
<!-- 普通插槽:直接渲染父组件传来的内容 -->
<slot></slot>
<!-- 作用域插槽:将子组件数据传递给父组件 -->
<slot :childMessage="childMessage"></slot>
</div>
</template>
<script>
export default {
data() {
return {
childMessage: '子组件的数据' // 子组件作用域
}
}
}
</script>编译时 vs 运行时
普通插槽:编译时确定
// 父组件模板
<template>
<child>
<span>{{ message }}</span> <!-- message 在编译时就绑定到父组件 -->
</child>
</template>
// 编译后的渲染函数(简化)
render() {
// 在父组件作用域中创建 VNode
const children = [createVNode('span', null, this.message)]
// 传递给子组件
return h(Child, null, { default: () => children })
}作用域插槽:运行时确定
// 父组件模板
<template>
<child>
<template v-slot="props">
<span>{{ props.message }}</span> <!-- message 来自子组件 -->
</template>
</child>
</template>
// 编译后的渲染函数(简化)
render() {
// 父组件不直接创建 VNode,而是创建一个函数
const scopedSlotFn = (props) => {
return createVNode('span', null, props.message)
}
// 把这个函数传递给子组件
return h(Child, null, { default: scopedSlotFn })
}
// 子组件中
render() {
// 子组件调用这个函数,传入自己的数据
const vnode = this.$scopedSlots.default({ message: this.childMessage })
return vnode
}总结对比表
| 维度 | 普通插槽 | 作用域插槽 |
|---|---|---|
| 数据来源 | 父组件 | 子组件 |
| 存储形式 | VNode数组 | 函数 |
| 编译时机 | 父组件编译时 | 子组件运行时调用 |
| 使用场景 | 布局、内容填充 | 自定义渲染,列表渲染 |
| 灵活性 | 低(内容固定) | 高(可动态渲染) |
| 数据流向 | 父 → 子(仅传递内容) | 子 → 父 (仅传递数据) |
vue3变化,scopedSlots被移除,统一使用scopedSlots被移除,统一使用scopedSlots被移除,统一使用slots,所有插槽都是函数。
<!-- 子组件 Child.vue -->
<template>
<div>
<!-- 普通插槽 -->
<slot></slot>
<!-- 作用域插槽 -->
<slot name="item" :data="itemData"></slot>
</div>
</template>
<script setup>
import { onMounted } from 'vue'
const itemData = { name: 'Vue 3', version: 3 }
onMounted(() => {
// Vue 3 中,所有插槽都是函数
console.log(typeof $slots.default) // 'function'
console.log(typeof $slots.item) // 'function'
// 调用函数获取 VNode
const defaultVNode = $slots.default()
const itemVNode = $slots.item({ data: itemData })
// 注意:Vue 3 中 $slots 返回的是 VNode 数组
console.log(Array.isArray(defaultVNode)) // true
})
</script>| 特性 | Vue 2 | Vue 3 |
|---|---|---|
| 普通插槽存储 | $slots (VNode 数组) | $slots (函数) |
| 作用域插槽存储 | $scopedSlots (函数) | $slots (函数) |
| 模板语法 | slot + slot-scope | 统一 v-slot 或 # |
| 访问方式 | this.$slots / this.$scopedSlots | useSlots() 或 $slots |
| 类型判断 | Array.isArray($slots.default) | typeof $slots.default === 'function' |
| 调用方式 | 普通插槽直接使用,作用域插槽需调用 | 所有插槽都需调用 |
到此这篇关于Vue2、Vue3中的$scopedSlots和$slots区别小结的文章就介绍到这了,更多相关Vue3中的$scopedSlots和$slots区别内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
vue element-plus中el-input修改边框border的方法
element样式还是蛮好的,只是有时候我们需要做一些调整,比如el-input的边框,下面这篇文章主要给大家介绍了关于vue element-plus中el-input修改边框border的相关资料,需要的朋友可以参考下2022-09-09
vue3+elementui-plus实现无限递归菜单示例代码
这篇文章主要介绍了vue3+elementui-plus实现无限递归菜单,当一个组件的 key 值发生变化时,Vue 会认为这是一个新的组件实例,会强制重新创建和渲染这个组件,本文通过示例代码详细讲解,需要的朋友可以参考下2024-04-04
proxy代理不生效以及vue config.js不生效解决方法
在开发Vue项目过程中,使用了Proxy代理进行数据劫持,但是在实际运行过程中发现代理并没有生效,也就是说数据并没有被劫持,这篇文章主要给大家介绍了关于proxy代理不生效以及vue config.js不生效解决方法的相关资料,需要的朋友可以参考下2023-11-11


最新评论