Skip to content

插槽

插槽可以让一个父组件调用子组件时,给子组件传入Html内容

当我们想封装一个按钮,按钮的内容可以通过prop传递到子组件,但是如果我们想给按钮添加icon图标呢,插槽是个不错的选择

1.默认插槽

通过一个slot标签,我们可以指定一个插槽

vue
<template>
	<button>
    	<slot></slot> 
    </button>
</template>
vue
<template>
	<MyButton>
        <Icon type="search" /> 搜索
    </MyButton>
</template>

想要在有插槽的子组件中插入html,子组件必须是双标签,写在双标签里的内容都会在子组件中的slot位置显示

slot标签最终不会渲染成一个真实的dom标签,可以理解为一个占位符

上方代码效果如下图所示

image-20241023143226753

2.插槽内容与出口

插槽的内容在父组件中定义,出口在子组件中定义

也就是说,在父组件中,给子组件的双标签内添加html内容,最终会被渲染到子组件中相应的位置

vue
<Child>
  Click me! <!-- 插槽内容 -->
</Child>
vue
<button class="fancy-btn">
  <slot></slot> <!-- 插槽出口 -->
</button>

<slot> 元素是一个插槽出口 (slot outlet),标示了父元素提供的插槽内容 (slot content) 将在哪里被渲染。

插槽图示

最终渲染出的 DOM 是这样:

html
<button class="fancy-btn">Click me!</button>

插槽内容可以是任意合法的模板内容,不局限于文本。例如我们可以传入多个元素,甚至是组件:

vue
<FancyButton>
  <span style="color:red">Click me!</span>
  <AwesomeIcon name="plus" />
</FancyButton>

3.默认内容

在外部没有提供任何内容的情况下,可以为插槽指定默认内容。

只需在<slot>标签中写入需要的内容

vue
<button type="submit">
  <slot>
    Submit <!-- 默认内容 -->
  </slot>
</button>

此时父组件中使用该组件

vue
<template>
	<MyButton></MyButton>
</template>

最后的button如下所示:

image-20241023143612537

4.具名插槽

可以在一个子组件的不同地方指定多个插槽,同时给这些slot标签定义一个name属性用于区分

没有提供 name<slot> 出口会隐式地命名为“default”。

vue
<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>

在使用的时候使用v-solt:soltName将指定的内容分传给不同的插槽

vue
<BaseLayout>
  <template v-slot:header>
    <!-- header 插槽的内容放这里 -->
  </template>
    
  <template>
    <!-- 没指定的内容会被放进默认插槽里 -->
  </template>
    
  <template v-slot:footer>
    <!-- footer 插槽的内容放这里 -->
  </template>
</BaseLayout>

v-slot 有对应的简写 #,因此 <template v-slot:header> 可以简写为 <template #header>。其意思就是“将这部分模板片段传入子组件的 header 插槽中”。

vue
<BaseLayout>
  <template #header>
    <h1>头部标题</h1>
  </template>

  <template #default>
    <p>文章具体内容</p>
  </template>

  <template #footer>
    <p>页尾</p>
  </template>
</BaseLayout>

当一个组件同时接收默认插槽和具名插槽时,所有位于顶级的非 <template> 节点都被隐式地视为默认插槽的内容。所以上面也可以写成:

vue
<BaseLayout>
    <template #header>
		<h1>头部标题</h1>
    </template>

    <!-- 隐式的默认插槽 -->
    <p>文章具体内容</p>

    <template #footer>
		<p>页尾</p>
    </template>
</BaseLayout>

5.条件插槽

条件插槽可以根据插槽是否存在来渲染某些内容

在下面的示例中,我们定义了一个卡片组件,它拥有三个条件插槽:headerfooterdefault。 当 header、footer 或 default 存在时,我们希望包装它们以提供额外的样式:

vue
<template>
  <div class="card">
    <div v-if="$slots.header" class="card-header">
      <slot name="header" />
    </div>
    
    <div v-if="$slots.default" class="card-content">
      <slot />
    </div>
    
    <div v-if="$slots.footer" class="card-footer">
      <slot name="footer" />
    </div>
  </div>
</template>

6.动态插槽名

动态指令参数v-slot 上也是有效的,即可以定义下面这样的动态插槽名:

vue
<base-layout>
  <template v-slot:[dynamicSlotName]>
    ...
  </template>

  <!-- 缩写为 -->
  <template #[dynamicSlotName]>
    ...
  </template>
</base-layout>

<script>
import {ref} from 'vue'
 
const dynamicSlotName = ref('name')
</script>

7.作用域插槽

作用域插槽可以在父组件中访问到子组件放在插槽上的数据

在子组件中,直接在slot标签上传递属性

vue
<!-- <MyComponent> 的模板 -->
<div>
  <slot :text="greetingMessage" :count="1"></slot>
</div>

在父组件中使用v-slot指令,指令值的名字可以自定义,指令的值为一个对象,对象中包含子组件在slot标签上传来的数据

vue
<MyComponent v-slot="slotProps">
  {{ slotProps.text }} {{ slotProps.count }}
</MyComponent>

image-20241023150641891

v-slot="slotProps" 可以类比这里的函数签名,和函数的参数类似,我们也可以在 v-slot 中使用解构:

vue
<MyComponent v-slot="{ text, count }">
  {{ text }} {{ count }}
</MyComponent>

作用域插槽可以和具名插槽一块使用

vue
<MyComponent>
  <template #header="headerProps">
    {{ headerProps }}
  </template>
</MyComponent>

向具名插槽中传入 props:

vue
<slot name="header" message="hello"></slot>

注意插槽上的 name 为了定义具名插槽而特别定义的属性,不会作为 props 传递给插槽。因此最终 headerProps 的结果是 { message: 'hello' }

当作用域插槽和默认插槽使用时,默认插槽必须要显式的使用 <template> 标签,不然插槽传递的数据无法传递过来

vue
<MyComponent>
  <!-- 使用显式的默认插槽 -->
  <template #default="{ message }">
    <p>{{ message }}</p>
  </template>

  <template #footer>
    <p>Here's some contact info</p>
  </template>
</MyComponent>
vue
<div>
  <slot :message="hello"></slot>
  <slot name="footer" />
</div>

MIT Licensed