插槽 Slots
组件用来接收模板内容
插槽内容与出口
<slot> 元素是一个插槽出口 (slot outlet),,标示了父元素提供的插槽内容 (slot content) 将在哪里被渲染。
插槽内容可以是任意合法的模板内容,不局限于文本。例如我们可以传入多个元素,甚至是组件:
<FancyButton><span style="color:red">Click me!</span><AwesomeIcon name="plus" />
</FancyButton>
渲染作用域
插槽内容可以访问到父组件的数据作用域,因为插槽内容本身是在父组件模板中定义的。举例来说:
<span>{{ message }}</span>
<FancyButton>{{ message }}</FancyButton>
这里的两个 {{ message }} 插值表达式渲染的内容都是一样的。
插槽内容无法访问子组件的数据。
父组件模板中的表达式只能访问父组件的作用域;子组件模板中的表达式只能访问子组件的作用域。
默认内容
在外部没有提供任何内容的情况下,可以为插槽指定默认内容。
<SubmitButton\> 组件
<button type="submit"><slot></slot>
</button>
如果我们想在父组件没有提供任何插槽内容时在 <button> 内渲染“Submit”,只需要将“Submit”写在 <slot> 标签之间来作为默认内容:
<button type="submit"><slot>Submit <!-- 默认内容 --></slot>
</button>
如果写入内容
<SubmitButton>Save</SubmitButton>
则会被渲染为
<button type="submit">Save</button>
具名插槽
有时在一个组件中包含多个插槽出口是很有用的。举例来说,在一个 <BaseLayout\> 组件中,有如下模板:
<div class="container"><header><!-- 标题内容放这里 --></header><main><!-- 主要内容放这里 --></main><footer><!-- 底部内容放这里 --></footer>
</div>
对于这种场景,<slot> 元素可以有一个特殊的 attribute name,用来给各个插槽分配唯一的 ID,以确定每一处要渲染的内容:
<div class="container"><header><slot name="header"></slot></header><main><slot></slot></main><footer><slot name="footer"></slot></footer>
</div>
这类带 name 的插槽被称为具名插槽 (named slots)。没有提供 name 的 <slot\> 出口会隐式地命名为“default”。
使用具名插槽
要为具名插槽传入内容,我们需要使用一个含 v-slot 指令的 <template> 元素,并将目标插槽的名字传给该指令:
<BaseLayout><template v-slot:header><!-- header 插槽的内容放这里 --></template>
</BaseLayout>
-
v-slot 有对应的简写 “#”,
-
<template v-slot:header\> 简写为<template #header\>其意思就是“将这部分模板片段传入子组件的 header 插槽中”。
完整代码
<BaseLayout><template #header><h1>Here might be a page title</h1></template><template #default><p>A paragraph for the main content.</p><p>And another one.</p></template><template #footer><p>Here's some contact info</p></template>
</BaseLayout>
当一个组件同时接收默认插槽和具名插槽时,所有位于顶级的非 <template> 节点都被隐式地视为默认插槽的内容。所以上面也可以写成:
<BaseLayout><template #header><h1>Here might be a page title</h1></template><!-- 隐式的默认插槽 --><p>A paragraph for the main content.</p><p>And another one.</p><template #footer><p>Here's some contact info</p></template>
</BaseLayout>
使用 JavaScript 函数来类比可能更有助于你来理解具名插槽:
// 传入不同的内容给不同名字的插槽
BaseLayout({header: `...`,default: `...`,footer: `...`
})// <BaseLayout> 渲染插槽内容到对应位置
function BaseLayout(slots) {return `<div class="container"><header>${slots.header}</header><main>${slots.default}</main><footer>${slots.footer}</footer></div>`
}
动态插槽名
动态指令参数在 v-slot 上也是有效的,即可以定义下面这样的动态插槽名:
<base-layout><template v-slot:[dynamicSlotName]>...</template><!-- 缩写为 --><template #[dynamicSlotName]>...</template>
</base-layout>
注意这里的表达式和动态指令参数受相同的语法限制:
-
动态参数值的限制
动态参数中表达式的值应当是一个字符串,或者是 null。特殊值 null 意为显式移除该绑定。其他非字符串的值会触发警告。
-
动态参数语法的限制
动态参数表达式因为某些字符的缘故有一些语法限制,比如空格和引号,在 HTML attribute 名称中都是不合法的。
作用域插槽
场景 : 插槽的内容可能想要同时使用父组件域内和子组件域内的数据。要做到这一点,我们需要一种方法来让子组件在渲染时将一部分数据提供给插槽。
我们也确实有办法这么做!可以像对组件传递 props 那样,向一个插槽的出口上传递 attributes:
<!-- <MyComponent> 的模板 -->
<div><slot :text="greetingMessage" :count="1"></slot>
</div>
默认插槽接收插槽 props
<MyComponent v-slot="slotProps">{{ slotProps.text }} {{ slotProps.count }}
</MyComponent>
你可以将作用域插槽类比为一个传入子组件的函数。子组件会将相应的 props 作为参数传给它:
MyComponent({// 类比默认插槽,将其想成一个函数default: (slotProps) => {return `${slotProps.text} ${slotProps.count}`}
})function MyComponent(slots) {const greetingMessage = 'hello'return `<div>${// 在插槽函数调用时传入 propsslots.default({ text: greetingMessage, count: 1 })}</div>`
}
实际上,这已经和作用域插槽的最终代码编译结果、以及手动编写渲染函数时使用作用域插槽的方式非常类似了。
具名作用域插槽
具名作用域插槽的工作方式也是类似的,插槽 props 可以作为 v-slot 指令的值被访问到:v-slot:name="slotProps"。当使用缩写时是这样:
<MyComponent><template #header="headerProps">{{ headerProps }}</template><template #default="defaultProps">{{ defaultProps }}</template><template #footer="footerProps">{{ footerProps }}</template>
</MyComponent>
向具名插槽中传入 props:
<slot name="header" message="hello"></slot>
注意插槽上的 name 是一个 Vue 特别保留的 attribute,不会作为 props 传递给插槽。因此最终 headerProps 的结果是 { message: 'hello' }。
如果你同时使用了具名插槽与默认插槽,则需要为默认插槽使用显式的 标签。尝试直接为组件添加 v-slot 指令将导致编译错误。
<!-- 该模板无法编译 -->
<template><MyComponent v-slot="{ message }"><p>{{ message }}</p><template #footer><!-- message 属于默认插槽,此处不可用 --><p>{{ message }}</p></template></MyComponent>
</template>
为默认插槽使用显式的 <template> 标签有助于更清晰地指出 message 属性在其他插槽中不可用:
<template><MyComponent><!-- 使用显式的默认插槽 --><template #default="{ message }"><p>{{ message }}</p></template><template #footer><p>Here's some contact info</p></template></MyComponent>
</template>