Vue组件通信之slot

文章目录

总结

  • 通过 slot (插槽)可实现"父组件向子组件传递模板内容"的功能
    • 子组件使用 <slot> 来占位,称为"插槽出口"(slot outlet)
    • 父组件提供"插槽内容"(slot content)
  • 插槽内容可以访问到父组件的数据作用域,但无法访问子组件的数据
  • 子组件可以为插槽指定默认内容。在父组件没有指定插槽内容时,默认内容会被渲染
  • 子组件可包含多个插槽,用 name 属性来区分不同插槽
    • 没有提供 name 的插槽会隐式地命名为 default
  • 在父组件里,可通过 v-slot 指令指定插槽
    • v-slot 可以简写为 #
  • 默认插槽和命名插槽可以混合使用,此时所有位于顶级的非 <template> 节点都被隐式地视为默认插槽的内容
  • 在子组件里,可以通过 $slots 来获取插槽内容
    • 常见的操作是判断父组件是否为某个插槽提供了内容
  • 通过作用域插槽,父组件可以获取子组件的数据
    • 子组件:在 <slot> 里通过 v-bind 绑定数据
    • 父组件:通过 v-slot 属性获取这些数据
  • 无渲染组件:组件只包括了逻辑,而没有渲染内容,把数据通过作用域插槽传递给父组件

环境

  • Ubuntu 24.04
  • Chrome Version 146.0.7680.164 (Official Build) (64-bit)
  • VSCode 1.109.5
  • npm 11.6.2
  • Vue 3.5.32

准备

创建一个Vue项目。创建过程略,具体可参见 https://blog.csdn.net/duke_ding2/article/details/159510007

关于如何导入和注册组件,参见 https://blog.csdn.net/duke_ding2/article/details/159472143

关于props,参见 https://blog.csdn.net/duke_ding2/article/details/159696940

关于emit,参见 https://blog.csdn.net/duke_ding2/article/details/159779784

关于v-model,参见 https://blog.csdn.net/duke_ding2/article/details/159829571

背景

在前面的文档中,我们了解了父子组件之间的一些通信方式:

  • 父组件如何通过自定义属性向子组件传送数据
  • 父组件如何通过自定义事件接收子组件的事件
  • 父组件如何通过 v-model 实现和子组件数据的双向绑定(本质是 propsemit 的组合)

现在,一个新的需求是,父组件在使用子组件时,想要传送一些模板片段,嵌入到子组件里面。

slot

通过Vue的 slot (插槽),可实现"父组件向子组件传递模板内容"的功能。

创建 MyComponent.vue 文件如下:

html 复制代码
<template>
    <div>
        <div>开始</div>
        <slot/>
        <div>结束</div>
    </div>
</template>

可见,在子组件里,定义了一部分模板内容,另一部分则使用 <slot> 来占位,称为"插槽出口"(slot outlet),这部分的内容称为"插槽内容"(slot content),是由父组件来定义的。

修改 App.vue 如下:

html 复制代码
<template>
    <div>
        <MyComponent>
            <h2>标题</h2>
            <div style="color: blue;font-size: 20px;">内容......</div>
        </MyComponent>
    </div>
</template>

<script>
    import MyComponent from './MyComponent.vue';
    export default {
        components: {
            MyComponent
        },
    }
</script>

注意这段代码:

html 复制代码
        <MyComponent>
            <h2>标题</h2>
            <div style="color: blue;font-size: 20px;">内容......</div>
        </MyComponent>

父组件在使用子组件时,传送了一些模板内容。这些内容将会被渲染在子组件的插槽出口处。

效果如下:

可见,父组件和子组件的模板内容合并显示出来了。最终渲染的效果相当于:

html 复制代码
    <div>
        <div>开始</div>
        <h2>标题</h2>
        <div style="color: blue;font-size: 20px;">内容......</div>
        <div>结束</div>
    </div>

作用域

因为插槽内容本身是在父组件模板中定义的,所以插槽内容可以访问到父组件的数据作用域,但无法访问子组件的数据。

也就是说:父组件模板中的表达式只能访问父组件的作用域;子组件模板中的表达式只能访问子组件的作用域。

默认内容

在父组件没有指定插槽内容时,子组件可以为插槽指定默认内容。

修改 MyComponent.vue 如下:

html 复制代码
<template>
    <div>
        <div>开始</div>
        <slot>这是默认内容</slot> <!-- 声明插槽时,可提供默认内容-->
        <div>结束</div>
    </div>
</template>

修改 App.vue 如下:

html 复制代码
<template>
    <div>
        <MyComponent/> <!-- 不提供插槽内容-->
        <br />
        <MyComponent>这是自定义内容</MyComponent> <!-- 提供插槽内容-->
    </div>
</template>

<script>
    import MyComponent from './MyComponent.vue';
    export default {
        components: {
            MyComponent
        },
    }
</script>

效果如下:

可见:

  • 若父组件提供了插槽内容,则显示父组件提供的内容
  • 若父组件不提供插槽内容,则显示子组件的默认插槽内容

命名插槽

上面的例子里,子组件都只有一个插槽。如果子组件有多个插槽,则需要通过 name 属性来区分不同的插槽。

注:没有提供 name 的插槽会隐式地命名为 default

修改 MyComponent.vue 如下:

html 复制代码
<template>
    <div>
        <div>标题开始</div>
        <slot name="title"/> <!-- 命名插槽 -->
        <div>标题结束</div>
        <br />
        <div>内容开始</div>
        <slot name="content"/> <!-- 命名插槽 -->
        <div>内容结束</div>
    </div>
</template>

可见,子组件声明了两个插槽,分别对应标题和内容。

修改 App.vue 如下:

html 复制代码
<template>
    <MyComponent>
        <template v-slot:title>这是标题</template>
        <template v-slot:content>这是内容</template>
    </MyComponent>
</template>

<script>
    import MyComponent from './MyComponent.vue';
    export default {
        components: {
            MyComponent
        },
    }
</script>

可见,如果子组件里有多个插槽,则在父组件里,需要通过 v-slot 指令指定插槽。

v-slot 可以简写为 #

默认插槽和命名插槽可以混合使用。此时所有位于顶级的非 <template> 节点都被隐式地视为默认插槽的内容。

修改 MyComponent.vue 如下:

html 复制代码
<template>
    <div>
        <div>标题开始</div>
        <slot name="title"/> <!-- 命名插槽 -->
        <div>标题结束</div>
        <br />
        <div>内容开始</div>
        <slot name="content"/> <!-- 命名插槽 -->
        <div>内容结束</div>
        <br />
        <div>remark开始</div>
        <slot /> <!-- 默认插槽 -->
        <div>remark结束</div>
    </div>
</template>

修改 App.vue 如下:

html 复制代码
<template>
    <MyComponent>
        <template v-slot:title>这是标题</template>
        <template v-slot:content>这是内容</template>
        这是remark
    </MyComponent>
</template>

<script>
    import MyComponent from './MyComponent.vue';
    export default {
        components: {
            MyComponent
        },
    }
</script>

效果如下:

条件插槽

在子组件里,可以通过 $slots 来获取插槽内容。常见的操作是判断父组件是否为某个插槽提供了内容。

修改 MyComponents.vue 如下:

html 复制代码
<template>
    <div>
        <div v-if="$slots.title">
            <div>标题开始</div>
            <slot name="title"/> <!-- 命名插槽 -->
            <div>标题结束</div>
        </div>
        <div v-if="$slots.content">
            <br />
            <div>内容开始</div>
            <slot name="content"/> <!-- 命名插槽 -->
            <div>内容结束</div>
        </div>
        <div v-if="$slots.default">
            <br />
            <div>remark开始</div>
            <slot /> <!-- 默认插槽 -->
            <div>remark结束</div>
        </div>
    </div>
</template>

修改 App.vue 如下:

html 复制代码
<template>
    <MyComponent>
        <template v-slot:title>这是标题</template>
        这是remark
    </MyComponent>
</template>

<script>
    import MyComponent from './MyComponent.vue';
    export default {
        components: {
            MyComponent
        },
    }
</script>

效果如下:

作用域插槽

前面提到:插槽的内容无法访问到子组件的状态。

实际上,有办法可以把子组件的数据传递到父组件。具体方法为:

  • 子组件:在 <slot> 里通过 v-bind 绑定数据
  • 父组件:通过 v-slot 属性获取这些数据

默认插槽

修改 MyComponent.vue 如下:

html 复制代码
<template>
    <div>
        <slot :title="titleValue" :content="contentValue"/> <!-- title 和 content 是自定义属性 -->
    </div>
</template>

<script>
    export default {
        data() {
            return {
                titleValue: '这是子组件定义的标题',
                contentValue: '这是子组件定义的内容',
            }
        }
    }
</script>

修改 App.vue 如下:

html 复制代码
<template>
    <MyComponent v-slot="slotProps"> <!-- v-slot 属性的值可任意命名,代表包含所有自定义属性的对象 -->
        <div>{{ slotProps.title }}</div>
        <div>{{ slotProps.content }}</div>
    </MyComponent>
</template>

<script>
    import MyComponent from './MyComponent.vue';
    export default {
        components: {
            MyComponent
        },
    }
</script>

效果如下:

命名插槽

对于命名插槽,方法和默认插槽类似,唯一不同在于,父组件需要为 v-slot 属性指定 name 参数。

修改 MyComponent.vue 如下:

html 复制代码
<template>
    <div>
        <slot name="myslot1" :title="titleValue1" :content="contentValue1"/>
        <slot name="myslot2" :title="titleValue2" :content="contentValue2"/>
    </div>
</template>

<script>
    export default {
        data() {
            return {
                titleValue1: '这是子组件定义的标题1',
                contentValue1: '这是子组件定义的内容1',
                titleValue2: '这是子组件定义的标题2',
                contentValue2: '这是子组件定义的内容2',
            }
        }
    }
</script>

注意:两个插槽都有 titlecontent 属性,但它们之间相互独立。

修改 App.vue 如下:

html 复制代码
<template>
    <MyComponent>
        <template #myslot1="slotProps1">
            <div>{{ slotProps1.title }}</div>
            <div>{{ slotProps1.content }}</div>
        </template>
        <template #myslot2="slotProps2">
            <br />
            <div>{{ slotProps2.title }}</div>
            <div>{{ slotProps2.content }}</div>
        </template>
    </MyComponent>
</template>

<script>
    import MyComponent from './MyComponent.vue';
    export default {
        components: {
            MyComponent
        },
    }
</script>

可见,父组件中,对于不同的插槽,只需指定不同的对象(可任意命名)来接收数据。

效果如下:

多插槽中的默认插槽

前面提到,默认插槽会被显式命名为 default ,因此,在多插槽中,可用 default 来指定默认插槽。

修改 MyComponent.vue 如下:

html 复制代码
<template>
    <div>
        <slot name="myslot1" :title="titleValue1" :content="contentValue1"/>
        <slot name="myslot2" :title="titleValue2" :content="contentValue2"/>
        <slot :title="titleValue3" :content="contentValue3"/>
    </div>
</template>

<script>
    export default {
        data() {
            return {
                titleValue1: '这是子组件定义的标题1',
                contentValue1: '这是子组件定义的内容1',
                titleValue2: '这是子组件定义的标题2',
                contentValue2: '这是子组件定义的内容2',
                titleValue3: '这是子组件定义的标题3',
                contentValue3: '这是子组件定义的内容3',
            }
        }
    }
</script>

本例中,有两个命名插槽和一个默认插槽。

修改 App.vue 如下:

html 复制代码
<template>
    <MyComponent>
        <template #myslot1="slotProps1">
            <div>{{ slotProps1.title }}</div>
            <div>{{ slotProps1.content }}</div>
        </template>
        <template #myslot2="slotProps2">
            <br />
            <div>{{ slotProps2.title }}</div>
            <div>{{ slotProps2.content }}</div>
        </template>
        <template #default="slotProps3">
            <br />
            <div>{{ slotProps3.title }}</div>
            <div>{{ slotProps3.content }}</div>
        </template>
    </MyComponent>
</template>

<script>
    import MyComponent from './MyComponent.vue';
    export default {
        components: {
            MyComponent
        },
    }
</script>

可见,由于有多个插槽,因此在指定默认插槽时,必须显式指定其名字 default

效果如下:

无渲染组件

一些组件可能只包括了逻辑,而没有渲染内容,把数据通过作用域插槽传递给父组件。这种类型的组件称为无渲染组件。

比如:

html 复制代码
<template>
    <slot :title="titleValue" :content="contentValue"/>
</template>

<script>
    export default {
        data() {
            return {
                titleValue: '这是子组件定义的标题',
                contentValue: '这是子组件定义的内容',
            }
        }
    }
</script>

该组件没有任何渲染,只负责数据和逻辑。

参考

  • https://cn.vuejs.org/guide/essentials/component-basics.html
  • https://cn.vuejs.org/guide/components/slots
相关推荐
布局呆星2 小时前
Vue3+TS 笔记:Props 与 Emits 的正确打开方式
javascript·vue.js·笔记
小李子呢02112 小时前
前端八股7--- Vue 状态管理工具(vuex和pinia)
前端·javascript·vue.js
Geoking.2 小时前
后端Long型数据传到前端js后精度丢失的问题(前后端传输踩坑指南)
java·前端·javascript·后端
oi..2 小时前
CSRF安全攻防:Referer 校验与 Token 防护详解
前端·网络·笔记·测试工具·安全·网络安全·csrf
申耀的科技观察2 小时前
【观察】昂瑞微5G射频前端通过车规认证,筑牢智能网联汽车通信安全“底座”
前端·5g·汽车
qq_260241232 小时前
将盾CDN:Web应用防火墙(WAF)的工作原理与实战配置
前端·网络·安全
旺王雪饼 www2 小时前
《Express框架深度解析:从基础入门到高级实践与项目架构》
前端·node.js·express
时寒的笔记2 小时前
js7逆向案例_禁止f12打开&sojson打开
开发语言·javascript·ecmascript
stpzhf2 小时前
uniapp nvue组件多个text在一行并且高亮其中一些文字
前端·javascript·uni-app