Skip to content

插槽

介绍

​ 通过选项props,子组件可以接收来自父组件的任意类型的 JavaScript 值,那有没有方式可以使得子组件接收到模板内容呢?特定情境下,我们使用子组件时可能需要传递一些模板片段,以便在子组件内动态地进行渲染。

​ 因此,Vue实现了一套内容分发的API,将 slot元素作为组件承载分发内容的插槽出口(也称为插槽),并把使用组件时所传入的模板片段作为插槽内容。简单来说,插槽就是子组件提供给父组件的一个占位符,父组件可以在这个占位符中填充任何模板代码,如HTML片段、组件等。

​ 例如,有一个名为child-node的组件,其模板代码如下所示,框中的内容为一个插槽出口(以下统称为插槽)。

插槽出口

​ 父组件使用child-node子组件时的模板代码如下,框中的内容为一个插槽内容。

插槽内容

​ 当在编译父组件模板时,子组件child-node随之被编译。在编译过程中,子组件child-node中的插槽slot会被父组件传递的插槽内容所替换。

插槽编译过程

注意:插槽内容中的模板片段除了可以书写HTML结构,还可以包含组件。

渲染作用域

​ 由于插槽内容本身是在父组件的模板中定义的,所以插槽内容只能访问父组件的数据作用域,无法访问子组件中的数据。

​ 其实,Vue模板中的表达式只能访问其定义时所处的作用域,这与JavaScript的词法作用域规则一致。当然,父组件中的插槽内容可以通过数据传递的方式使用子组件当中的数据,这一点会在以下作用域插槽中讲述。

默认内容

​ 子组件可以定义插槽的默认内容,当外部(父组件)没有提供任何内容的情况下,默认内容会被渲染。

​ 例如,在child-node组件的模板中,添加如下代码,框中的内容便为插槽的默认内容。

插槽默认内容

​ 当父组件通过以下方式引用子组件时,child-node中插槽的默认内容便会被渲染。

  1. 单标签

单标签引用

  1. 只存在空格符和换行符

只存在空格和换行

具名插槽

​ 一个组件可以包含多个插槽,若同一个插槽内容指向多个插槽时,这份内容会被渲染成多份。但往往,我们希望子组件中的多个插槽可以区分类型,并且在父组件引用子组件时能够指定不同的插槽内容分发至对应的插槽。

​ 而Vue恰好给我们提供了这样一种能力,那就是具名插槽,具名插槽是指带 name属性的插槽,而依靠这个 name属性就能够做到不同种类插槽的区分。没有显式指定 name属性的 slot元素也被称为默认插槽,在编译模板时其 name属性也会隐式地被命名为default

​ 例如,有一个名为fg-panel的组件,其模板代码如下所示,框中的内容为两个具名插槽,name分别为headerfooter

具名插槽模板

​ 在父组件的模板中,可使用指令v-slot将不同插槽内容分发至对应的插槽。在使用具名插槽时,模板片段必须使用 <template>元素包裹,具体语法为 v-slot:后跟插槽的 name,如下图。

分发具名插槽内容

​ 此外,Vue还提供一种简写语法,可将v-slot:简写成 #。具体可见下图,下图根据上图进行了语法简写。

分发具名插槽内容简写

注意

  1. 在子组件标签内容中,所有位于顶级的非 <template>节点都会被隐式地视为默认插槽的内容,且可以分段出现。
  2. 存在多个name值相同的<template>时,顺序靠下的会覆盖顺序靠上的。(不建议这样使用)

动态插槽名

​ 动态插槽名是2.6.0版本中新增的特性,用于根据运行时的数据或条件动态地选择或切换插槽。语法为v-slot:[](或#[]),[]中支持对象语法,例如datamethodscomputed,如下图。

动态插槽名

作用域插槽

​ 在上面的渲染作用域中讲到,插槽内容无法访问到子组件的数据,但我们可以通 过数据传递的方式在插槽内容中使用子组件中的数据。这种数据传递的方式称为作用域插槽,其与选项props类似,但数据传递的方向相反。

​ 例如,有一个名为fg-panel的组件,模板代码如下所示。模板中存在一个nameheader的具名插槽,而header-listheader-content则是插槽向插槽内容传递的prop。

作用域插槽

​ 在父组件的插槽内容中如何获取这些属性呢?其实每个插槽传递的所有prop都会被包装成一个对象,在插槽内容中作为指令v-slot(或#)的值出现,如下图。

使用作用域插槽

​ 作用域插槽的数据传递可以类比函数的参数传递,所以我们也可以在v-slot中使用解构,如下图。

解构使用作用域插槽

注意

  1. 作用域插槽不需要满足单向数据流,数据通过函数进行传递。

  2. 当作用域插槽为默认插槽时,若想获取prop对象,需要显式地使用<tempalte>标签包裹插槽内容,可使用v-slot="slotProps"v-slot:default="slotProps"#default="slotProps"这三种语法。不支持**#="slotProps"**,该方式存在语法错误。

风格指南:在插槽<slot>中的prop应使用kebab-case风格书写,接收JavaScript变量时应使用**camelCase **风格书写。

废弃语法

​ 与动态插槽名相同的是,指令v-slot#)这种插槽语法也是在2.6.0版本中引入的,而原本的旧语法已被废弃。在Vue所有的2.x版本中,这种废弃语法依旧被支持,但在Vue的3.x版本中已完全不支持。

​ 废弃的插槽内容语法为attribute slotslot-scopeslot属性为指定具名插槽的nameslot-scope属性为获取插槽出口所传递的prop包装对象。

​ 接下来,我们来对比一下新旧语法。例如,现在我们要将模板片段中的某个插槽内容分发给name值为header的插槽<slot>,且需要接收<slot>所传递的prop对象。

vue
<!-- 2.6.0新语法 -->
<template #header="scope">
  <!-- template content -->
</template>
<!--  --------------------分割线--------------------  -->
<!-- 废弃的语法 -->
<template slot="header" slot-scope="scope">
  <!-- template content -->
</template>

​ 显然,v-slot指令语法(上述为简写方式)更加得简洁。

注意slot,slot-scopev-slot类似,只是语法不同。例如,当slot="default",该键值对可以直接忽略不写,但有以下几点不同。

  1. 插槽属性slotslot-scope都可以直接用于<template>元素(包括组件)。
  2. 在使用 slot 时,如果存在多个相同名称的插槽,通常情况下它们不会相互覆盖,无论是否使用 <template> 标签进行包裹。然而,如果为插槽指定了 slot-scope,那么它的优先级会比没有指定 slot-scope 的插槽高(都指定则比较顺序)。

上次更新于: