• 插槽 Slots:用于传递模板内容。

插槽内容与出口

  1. https://cn.vuejs.org/guide/components/slots.html#slot-content-and-outlet
  2. 子组件 FancyButton.vue
1
2
3
4
5
6
<template>
    <button class="fancy-btn">
        <!-- 插槽内容等待传入的内容 -->
        <slot></slot>
    </button>
</template>
  1. 父组件 App.vue
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<template>
  <div>
    <FancyButton>
        这里是插槽内容
    </FancyButton>
  </div>
</template>

<script setup>
import FancyButton from './components/FancyButton.vue';
</script>
  1. 渲染后的结构。
<div>
    <button class="fancy-btn">这里是插槽内容</button>
</div>

渲染作用域

  1. https://cn.vuejs.org/guide/components/slots.html#render-scope
  2. 子组件 FancyButton.vue
1
2
3
4
5
<template>
    <button class="fancy-btn">
        <slot></slot>
    </button>
</template>
  1. 父组件 App.vue
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<template>
  <div>
    <span>{{ message }}</span>
    <FancyButton>
        {{ message }}
    </FancyButton>
  </div>
</template>

<script setup>
import FancyButton from './components/FancyButton.vue';
import { ref } from 'vue'

const message = ref('hello, world!')
</script>

默认内容

  1. https://cn.vuejs.org/guide/components/slots.html#fallback-content
  2. 子组件 SubmitButton.vue
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<template>
    <button type="submit">
        <slot>
            Submit <!-- 默认内容 -->
        </slot>
    </button>
</template>

<script setup>

</script>
  1. 父组件 App.vue
1
2
3
4
5
6
7
8
9
<template>
  <div>
    <SubmitButton />
  </div>
</template>

<script setup>
import SubmitButton from './components/SubmitButton.vue';
</script>

具名插槽

  1. https://cn.vuejs.org/guide/components/slots.html#named-slots
  2. 子组件 BaseLayout.vue
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<template>
<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <!-- 没有提供name的<slot>出口会隐式地命名为default -->
  <main>
    <slot></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>
</template>

<script setup>
</script>
  1. 父组件 App.vue
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<template>
  <div>
    <BaseLayout>
        <!-- v-slot:header 简写 #header -->
        <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>
  </div>
</template>

<script setup>
import BaseLayout from './components/BaseLayout.vue';
</script>
  1. 隐式的默认插槽。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<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>
  1. 渲染结果。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<div class="container">
  <header>
    <h1>Here might be a page title</h1>
  </header>
  <main>
    <p>A paragraph for the main content.</p>
    <p>And another one.</p>
  </main>
  <!-- footer标签会一直存在 -->
  <footer>
    <p>Here's some contact info</p>
  </footer>
</div>

条件插槽

  1. https://cn.vuejs.org/guide/components/slots.html#conditional-slots
  2. 子组件 BaseLayout.vue
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
<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标签不会存在 -->
    <div v-if="$slots.footer" class="card-footer">
      <slot name="footer" />
    </div>
  </div>
</template>

<script setup>

</script>
  1. 父组件 App.vue
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<template>
  <div>
    <BaseLayout>
        <!-- v-slot:header 简写 #header -->
        <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>
    </BaseLayout>
  </div>
</template>

<script setup>
import BaseLayout from './components/BaseLayout.vue';
</script>

动态插槽名

  1. https://cn.vuejs.org/guide/components/slots.html#dynamic-slot-names

作用域插槽

  1. https://cn.vuejs.org/guide/components/slots.html#scoped-slots
  2. 子组件 BaseLayout.vue
1
2
3
4
5
6
7
8
9
<template>
  <div>
    <slot :text="greetingMessage" :count="1"></slot>
  </div>
</template>

<script setup>
const greetingMessage = 'hello'
</script>
  1. 父组件 App.vue
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<template>
  <div>
    <BaseLayout v-slot="slotProps">
        {{ slotProps.text }} {{ slotProps.count }}
    </BaseLayout>
  </div>
</template>

<script setup>
import BaseLayout from './components/BaseLayout.vue';
</script>
  1. 使用解构写法。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<!-- App.vue -->
<template>
  <div>
    <BaseLayout v-slot="{ text, count }">
        {{ text }} {{ count }}
    </BaseLayout>
  </div>
</template>

<script setup>
import BaseLayout from './components/BaseLayout.vue';
</script>

具名作用域插槽

  1. https://cn.vuejs.org/guide/components/slots.html#named-scoped-slots
  2. 子组件 BaseLayout.vue
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<template>
  <div>
    <BaseLayout>
        <template #header="headerProps">
          <!-- { "message": "hello" } -->
          {{ headerProps }}
        </template>
    </BaseLayout>
  </div>
</template>

<script setup>
import BaseLayout from './components/BaseLayout.vue';
</script>
  1. 父组件 App.vue
1
2
3
4
5
6
7
8
9
<template>
  <div>
    <!-- name 是一个 Vue 特别保留的 attribute不会作为 props 传递给插槽 -->
    <slot name="header" message="hello"></slot>
  </div>
</template>

<script setup>
</script>
  1. 使用解构写法。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<!-- App.vue -->
<template>
  <div>
    <BaseLayout>
        <template #header="{ message }">
          {{ message }}
        </template>
    </BaseLayout>
  </div>
</template>

<script setup>
import BaseLayout from './components/BaseLayout.vue';
</script>

高级列表组件示例

  1. https://cn.vuejs.org/guide/components/slots.html#fancy-list-example
  2. 子组件 FancyList.vue
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<template>
  <ul>
    <li v-if="!items.length">
      Loading...
    </li>
    <li v-for="item in items">
      <slot name="item" v-bind="item"/>
    </li>
  </ul>
</template>

<script setup>
import { ref } from 'vue'

const props = defineProps(['api-url', 'per-page'])

const items = ref([])

// mock remote data fetching
setTimeout(() => {
  items.value = [
      { body: 'Scoped Slots Guide', username: 'Evan You', likes: 20 },
	  { body: 'Vue Tutorial', username: 'Natalia Tepluhina', likes: 10 }
  ]
}, 1000)
</script>

<style scoped>
  ul {
    list-style-type: none;
    padding: 5px;
    background: linear-gradient(315deg, #42d392 25%, #647eff);
  }
  li {
    padding: 5px 20px;
    margin: 10px;
    background: #fff;
  }
</style>
  1. 父组件 App.vue
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
<script setup>
import FancyList from './components/FancyList.vue';
</script>

<template>
  <FancyList api-url="url" :per-page="10">
    <template #item="{ body, username, likes }">
      <div class="item">
        <p>{{ body }}</p>
        <p class="meta">by {{ username }} | {{ likes }} likes</p>
      </div>
    </template>
  </FancyList>
</template>

<style scoped>
.meta {
  font-size: 0.8em;
  color: #42b883;
}
</style>

无渲染组件

  1. https://cn.vuejs.org/guide/components/slots.html#renderless-components