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
相关推荐
陈随易42 分钟前
有生之年系列,Nodejs进程管理pm2 v7.0发布
前端·后端·程序员
冰暮流星1 小时前
javascript之事件代理/事件委托
前端
陈随易2 小时前
AI时代,你还在坚持手搓文章吗
前端·后端·程序员
里欧跑得慢4 小时前
17. Flutter Hero动画实现:让界面过渡更加优雅
前端·css·flutter·web
IT_陈寒5 小时前
Vue的这个响应式陷阱,我debug了一整天才爬出来
前端·人工智能·后端
cn_mengbei5 小时前
用React Native开发OpenHarmony应用:Reanimated共享元素过渡
javascript·react native·react.js
kyriewen5 小时前
前端测试:别为了100%覆盖率而写测试,那是自欺欺人
前端·javascript·单元测试
去伪存真5 小时前
我自己写的第一个skills--project-core-standards
前端·agent
Data_Journal5 小时前
如何使用cURL更改User Agent
大数据·服务器·前端·javascript·数据库
掌心向暖RPA自动化5 小时前
如何获取网页某个元素在屏幕可见部分的中心坐标影刀RPA懒加载坐标定位技巧
java·javascript·自动化·rpa·影刀rpa