如果我們需要向組件傳遞元素或組件,這個時候就要用到 Slots 了。在三大框架中,它們對 這種傳遞方式的稱呼不同,為了方便起見,我們把這種向組件傳遞元素或組件的方式統(tǒng)一稱為 Slots 。
下表是三大框架中對于 Slot 的相關描述:
| 框架 | 傳遞元素或組件的稱呼 | 描述 |
|---|---|---|
| Angular | 內(nèi)容投影 | 內(nèi)容投影是一種模式,你可以在其中插入或投影要在另一個組件中使用的內(nèi)容。 |
| React | children prop | 可以將帶有 children prop 的組件看作有一個“洞”,可以由其父組件使用任意 JSX 來“填充”。 |
| Vue | 插槽 | 不單單是傳元素和組件用的,它本身還有作用域 |
Angular 組件的 Slots
在 Angular 組件中有單槽內(nèi)容投影,多槽內(nèi)容投影,條件內(nèi)容投影三種,它的投影還在生命周期的鉤子中有體現(xiàn)。
向組件傳遞單個 slot
單槽內(nèi)容投影即是向組件傳遞單個 slot , Slot 在 Angular 組件模板中使用 <ng-content> 元素來顯示。
組件 zippy-basic.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-zippy-basic',
template: `
<h2>Single-slot content projection</h2>
<ng-content></ng-content>
`
})
export class ZippyBasicComponent {}
模板 app.component.html
<app-zippy-basic>
<!-- 向組件傳遞的單個 slot 內(nèi)容 -->
<p>Is content projection cool?</p>
</app-zippy-basic>
向組件傳遞多個 slot
多槽內(nèi)容投影即是向組件傳遞多個 slot , 我們可以通過使用 <ng-content> 的 select 屬性來完成此任務。
組件 zippy-multislot.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-zippy-multislot',
template: `
<h2>Multi-slot content projection</h2>
Default:
<ng-content></ng-content>
Question:
<ng-content select="[question]"></ng-content>
`
})
export class ZippyMultislotComponent {}
使用 question 屬性的內(nèi)容將投影到帶有 select=[question] 屬性的 <ng-content> 元素。
模板 app.component.html
<app-zippy-multislot>
<p question>
Is content projection cool?
</p>
<p>Let's learn about content projection!</p>
</app-zippy-multislot>
我們還可以使用 ngProjectAs 屬性來完成此操作。
<ng-container ngProjectAs="[question]">
<p>Is content projection cool?</p>
</ng-container>
有了 ngProjectAs,就可以用 [question] 選擇器將整個 <ng-container> 元素投影到組件中。
向組件有條件的傳遞 slot
如果我們的組件需要有條件地渲染內(nèi)容或多次渲染內(nèi)容,則應配置該組件以接受一個 <ng-template> 元素,其中包含要有條件渲染的內(nèi)容。
在這種情況下,不建議使用 <ng-content> 元素,因為只要組件的使用者提供了內(nèi)容,即使該組件從未定義 <ng-content> 元素或該 <ng-content> 元素位于 ngIf 語句的內(nèi)部,該內(nèi)容也總會被初始化。
下面的這個例子,即使在條件為假時,HelloWorld 組件也總是會被初始化。
組件 example-zippy.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-example-zippy',
template: `
<h2>根據(jù) expanded 的狀態(tài)加載 slot</h2>
<button (click)="toggle()">Toggle</button>
<ng-content *ngIf="expanded"></ng-content>
`
})
export class ZippyBasicComponent {
expanded = false;
toggle() {
this.expanded = ! this.expanded;
}
}
模板 app.component.html
<app-example-zippy>
<app-hello-world></app-hello-world>
</app-example-zippy>
請注意,在上面那個例子中 HelloWorld 組件總是會被初始化。這與我們的初衷不同,正常情況下應該是 expanded 的值為 true 時 HelloWorld 組件才會被初始化。
下面我們來調(diào)整一下,只有當 expanded 的值為真時 HelloWorld 組件才會被初始化。
調(diào)整后的組件 example-zippy.component.ts
import { Component, Directive, TemplateRef, ContentChild } from '@angular/core';
@Directive({
selector: '[appZippyContent]'
})
export class ZippyExampleContentDirective {
constructor(public templateRef: TemplateRef<unknown>) {}
}
@Component({
selector: 'app-example-zippy',
template: `
<h2>Single-slot content projection</h2>
<button (click)="toggle()">Toggle</button>
<ng-container *ngIf="expanded" [ngTemplateOutlet]="content.templateRef"></ng-container>
`
})
export class ExampleZippyComponent {
expanded = false;
@ContentChild(ZippyExampleContentDirective) content!: ZippyExampleContentDirective;
toggle() {
this.expanded = ! this.expanded;
}
}
調(diào)整后的模板 app.component.html
<app-example-zippy>
<ng-template appZippyContent>
<app-hello-world></app-hello-world>
</ng-template>
</app-example-zippy>
React 組件的 Slots
在 React 組件中,使用一個名稱為 children 的 prop 來傳遞 JSX 。
向組件傳遞單個 slot
function Card({ children }) {
return (
<div className="card">
{children}
</div>
);
}
export default function Profile() {
return (
<Card>
<p>card content</p>
</Card>
);
}
向組件傳遞多個 slot
function Card({ children }) {
return (
<div className="card">
{children}
</div>
);
}
export default function Profile() {
return (
<Card>
<h2>Card Title</h2>
<p>card content</p>
</Card>
);
}
向組件有條件的傳遞 slot
import { useState } from 'react';
import { HelloWorld } from './HelloWorld.js'
function Card({ children }) {
const [showChildren, setShowChildren] = useState(false);
return (
<div className="card">
{ showChildren && children }
</div>
);
}
export default function Profile() {
return (
<Card>
<HelloWorld />
</Card>
);
}
Vue 組件的 Slots
在 Vue 組件中,它提供了默認插槽和具名插槽,類似于 Angular 中的單槽內(nèi)容投影和多槽內(nèi)容投影。另外它還提供了作用域插槽,作用域插槽是 Vue 框架獨有的功能,在本篇文章中不做介紹,感興趣的同學可以移步 Vue 官網(wǎng) 作用域插槽 一節(jié)中查看。
向組件傳遞單個 slot
默認插槽即是向組件傳遞單個 slot , Slot 在 Vue 組件模板中使用 slot 元素來顯示。
SubmitButton 組件代碼片段
<button type="submit">
<slot></slot>
</button>
插槽內(nèi)容
<SubmitButton>Save</SubmitButton>
向組件傳遞多個 slot
通過具名插槽指定的名稱可以向組件傳遞多個 slot 。
<slot> 元素可以有一個特殊的 attribute name,用來給各個插槽分配唯一的 ID 。沒有提供 name 的 <slot> 出口會隱式地命名為“default” 。
BaseLayout 組件代碼片段
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
要為具名插槽傳入內(nèi)容,我們需要使用一個含 v-slot 指令的 <template> 元素,并將目標插槽的名字傳給該指令,v-slot 有對應的簡寫 # 。
向 <BaseLayout> 傳遞插槽內(nèi)容的代碼
<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>
向組件有條件的傳遞 slot
如果需要在組件中有條件的傳遞 slot , 可以在組件中使用 v-if 指令來控制。
<script setup>
import { ref } from 'vue'
const showHeader = ref(false)
const showBody = ref(true)
const showFooter = ref(false)
</script>
<template>
<div class="container">
<header v-if="showHeader">
<slot name="header"></slot>
</header>
<main v-if="showBody">
<slot></slot>
</main>
<footer v-if="showFooter">
<slot name="footer"></slot>
</footer>
</div>
</template>
小結
本章介紹了三大框架組件的 Slots ,對組件如何傳遞 slot 做了說明。
Angular 中向組件有條件的傳遞 slot 時應該使用 <ng-template> 元素,這樣可以讓組件根據(jù)我們想要的任何條件顯式渲染內(nèi)容,如果使用 <ng-content> ,無論條件是否成立,總會初始化插槽的內(nèi)容。
React 組件中使用一個名稱為 children 的 prop 來傳遞 JSX ,其實它并不存在 Angular 與 Vue中的單 slot 與 多 slot 分類。
Vue 組件中提供了作用域插槽,這是其他兩個框架所不具備的功能,不過使用了作用域插槽后增加了組件之間的耦合性,筆者認為使用這個功能時還是需要慎重的。
文章參考鏈接: