如果我们需要向组件传递元素或组件,这个时候就要用到 Slots 了。在三大框架中,它们对 这种传递方式的称呼不同,为了方便起见,我们把这种向组件传递元素或组件的方式统一称为 Slots 。
下表是三大框架中对于 Slot 的相关描述:
框架 | 传递元素或组件的称呼 | 描述 |
---|---|---|
Angular | 内容投影 | 内容投影是一种模式,你可以在其中插入或投影要在另一个组件中使用的内容。 |
React | children prop | 可以将带有 children prop 的组件看作有一个“洞”,可以由其父组件使用任意 JSX 来“填充”。 |
Vue | 插槽 | 不单单是传元素和组件用的,它本身还有作用域 |
Angular 组件的 Slots
在 Angular 组件中有单槽内容投影,多槽内容投影,条件内容投影三种,它的投影还在生命周期的钩子中有体现。
向组件传递单个 slot
单槽内容投影即是向组件传递单个 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 内容 -->
<p>Is content projection cool?</p>
</app-zippy-basic>
向组件传递多个 slot
多槽内容投影即是向组件传递多个 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 属性的内容将投影到带有 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
如果我们的组件需要有条件地渲染内容或多次渲染内容,则应配置该组件以接受一个 <ng-template> 元素,其中包含要有条件渲染的内容。
在这种情况下,不建议使用 <ng-content> 元素,因为只要组件的使用者提供了内容,即使该组件从未定义 <ng-content> 元素或该 <ng-content> 元素位于 ngIf 语句的内部,该内容也总会被初始化。
下面的这个例子,即使在条件为假时,HelloWorld 组件也总是会被初始化。
组件 example-zippy.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-example-zippy',
template: `
<h2>根据 expanded 的状态加载 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 组件才会被初始化。
下面我们来调整一下,只有当 expanded 的值为真时 HelloWorld 组件才会被初始化。
调整后的组件 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;
}
}
调整后的模板 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 中的单槽内容投影和多槽内容投影。另外它还提供了作用域插槽,作用域插槽是 Vue 框架独有的功能,在本篇文章中不做介绍,感兴趣的同学可以移步 Vue 官网 作用域插槽 一节中查看。
向组件传递单个 slot
默认插槽即是向组件传递单个 slot , Slot 在 Vue 组件模板中使用 slot 元素来显示。
SubmitButton 组件代码片段
<button type="submit">
<slot></slot>
</button>
插槽内容
<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>
要为具名插槽传入内容,我们需要使用一个含 v-slot 指令的 <template> 元素,并将目标插槽的名字传给该指令,v-slot 有对应的简写 # 。
向 <BaseLayout> 传递插槽内容的代码
<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> 元素,这样可以让组件根据我们想要的任何条件显式渲染内容,如果使用 <ng-content> ,无论条件是否成立,总会初始化插槽的内容。
React 组件中使用一个名称为 children 的 prop 来传递 JSX ,其实它并不存在 Angular 与 Vue中的单 slot 与 多 slot 分类。
Vue 组件中提供了作用域插槽,这是其他两个框架所不具备的功能,不过使用了作用域插槽后增加了组件之间的耦合性,笔者认为使用这个功能时还是需要慎重的。
文章参考链接:
网友评论