插槽 Slots 此章节假设你已经看过了组件基础。若你还不了解组件是什么,请先阅读该章节。
插槽内容与出口 在之前的章节中,我们已经了解到组件能够接收任意类型的 JavaScript 值作为 props,但组件要如何接收模板内容呢?在某些场景中,我们可能想要为子组件传递一些模板片段,让子组件在它们的组件中渲染这些片段。
举例来说,这里有一个
template
Click me!
而
template
最终渲染出的 DOM 是这样:
html在演练场中尝试一下
在演练场中尝试一下
通过使用插槽,
理解插槽的另一种方式是和下面的 JavaScript 函数作类比,其概念是类似的:
js// 父元素传入插槽内容
FancyButton('Click me!')
// FancyButton 在自己的模板中渲染插槽内容
function FancyButton(slotContent) {
return `
${slotContent}
`
}插槽内容可以是任意合法的模板内容,不局限于文本。例如我们可以传入多个元素,甚至是组件:
template
Click me!
在演练场中尝试一下
在演练场中尝试一下
通过使用插槽,
Vue 组件的插槽机制是受原生 Web Component
渲染作用域 插槽内容可以访问到父组件的数据作用域,因为插槽内容本身是在父组件模板中定义的。举例来说:
template{{ message }}
插槽内容无法访问子组件的数据。Vue 模板中的表达式只能访问其定义时所处的作用域,这和 JavaScript 的词法作用域规则是一致的。换言之:
父组件模板中的表达式只能访问父组件的作用域;子组件模板中的表达式只能访问子组件的作用域。
默认内容 在外部没有提供任何内容的情况下,可以为插槽指定默认内容。比如有这样一个
template
如果我们想在父组件没有提供任何插槽内容时在
template
Submit
现在,当我们在父组件中使用
template
html但如果我们提供了插槽内容:
template
html在演练场中尝试一下
在演练场中尝试一下
具名插槽 有时在一个组件中包含多个插槽出口是很有用的。举例来说,在一个
template
template
在父组件中使用
要为具名插槽传入内容,我们需要使用一个含 v-slot 指令的 元素,并将目标插槽的名字传给该指令:
template
v-slot 有对应的简写 #,因此 可以简写为 。其意思就是“将这部分模板片段传入子组件的 header 插槽中”。
下面我们给出完整的、向
template
Here might be a page title
A paragraph for the main content.
And another one.
Here's some contact info
当一个组件同时接收默认插槽和具名插槽时,所有位于顶级的非 节点都被隐式地视为默认插槽的内容。所以上面也可以写成:
template
Here might be a page title
A paragraph for the main content.
And another one.
Here's some contact info
现在 元素中的所有内容都将被传递到相应的插槽。最终渲染出的 HTML 如下:
html
Here might be a page title
A paragraph for the main content.
And another one.
Here's some contact info
在演练场中尝试一下
使用 JavaScript 函数来类比可能更有助于你来理解具名插槽:
js// 传入不同的内容给不同名字的插槽
BaseLayout({
header: `...`,
default: `...`,
footer: `...`
})
//
function BaseLayout(slots) {
return `
}条件插槽 有时你需要根据内容是否被传入了插槽来渲染某些内容。
你可以结合使用 $slots 属性与 v-if 来实现。
在下面的示例中,我们定义了一个卡片组件,它拥有三个条件插槽:header、footer 和 default。 当 header、footer 或 default 的内容存在时,我们希望包装它以提供额外的样式:
template
在演练场中尝试一下
动态插槽名 动态指令参数在 v-slot 上也是有效的,即可以定义下面这样的动态插槽名:
template
...
...
注意这里的表达式和动态指令参数受相同的语法限制。
作用域插槽 在上面的渲染作用域中我们讨论到,插槽的内容无法访问到子组件的状态。
然而在某些场景下插槽的内容可能想要同时使用父组件域内和子组件域内的数据。要做到这一点,我们需要一种方法来让子组件在渲染时将一部分数据提供给插槽。
我们也确实有办法这么做!可以像对组件传递 props 那样,向一个插槽的出口上传递 attributes:
template
template
{{ slotProps.text }} {{ slotProps.count }}
在演练场中尝试一下
在演练场中尝试一下
子组件传入插槽的 props 作为了 v-slot 指令的值,可以在插槽内的表达式中访问。
你可以将作用域插槽类比为一个传入子组件的函数。子组件会将相应的 props 作为参数传给它:
jsMyComponent({
// 类比默认插槽,将其想成一个函数
default: (slotProps) => {
return `${slotProps.text} ${slotProps.count}`
}
})
function MyComponent(slots) {
const greetingMessage = 'hello'
return `
// 在插槽函数调用时传入 props
slots.default({ text: greetingMessage, count: 1 })
}
}实际上,这已经和作用域插槽的最终代码编译结果、以及手动编写渲染函数时使用作用域插槽的方式非常类似了。
v-slot="slotProps" 可以类比这里的函数签名,和函数的参数类似,我们也可以在 v-slot 中使用解构:
template
{{ text }} {{ count }}
具名作用域插槽 具名作用域插槽的工作方式也是类似的,插槽 props 可以作为 v-slot 指令的值被访问到:v-slot:name="slotProps"。当使用缩写时是这样:
template
{{ headerProps }}
{{ defaultProps }}
{{ footerProps }}
向具名插槽中传入 props:
template
如果你同时使用了具名插槽与默认插槽,则需要为默认插槽使用显式的 标签。尝试直接为组件添加 v-slot 指令将导致编译错误。这是为了避免因默认插槽的 props 的作用域而困惑。举例:
template
{{ message }}
{{ message }}
为默认插槽使用显式的 标签有助于更清晰地指出 message 属性在其他插槽中不可用:
template
{{ message }}
Here's some contact info
高级列表组件示例 你可能想问什么样的场景才适合用到作用域插槽,这里我们来看一个
template
{{ body }}
by {{ username }} | {{ likes }} likes
在
template
在演练场中尝试一下
无渲染组件 上面的
如果我们将这个概念拓展一下,可以想象的是,一些组件可能只包括了逻辑而不需要自己渲染内容,视图输出通过作用域插槽全权交给了消费者组件。我们将这种类型的组件称为无渲染组件。
这里有一个无渲染组件的例子,一个封装了追踪当前鼠标位置逻辑的组件:
template
Mouse is at: {{ x }}, {{ y }}
在演练场中尝试一下
在演练场中尝试一下
虽然这个模式很有趣,但大部分能用无渲染组件实现的功能都可以通过组合式 API 以另一种更高效的方式实现,并且还不会带来额外组件嵌套的开销。之后我们会在组合式函数一章中介绍如何更高效地实现追踪鼠标位置的功能。
尽管如此,作用域插槽在需要同时封装逻辑、组合视图界面时还是很有用,就像上面的