Angular React Vue 比較 – 組件篇之Slots

如果我們需要向組件傳遞元素或組件,這個時候就要用到 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 組件中提供了作用域插槽,這是其他兩個框架所不具備的功能,不過使用了作用域插槽后增加了組件之間的耦合性,筆者認為使用這個功能時還是需要慎重的。

文章參考鏈接:

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • Angular組件進階 前幾天, 解決了困擾許久的Angular組件自定義部分模板問題, 才準備梳理下知識, 有了...
    lijinfei閱讀 665評論 0 5
  • 探索Vue高階組件 高階組件(HOC)是 React 生態(tài)系統(tǒng)的常用詞匯,React 中代碼復用的主要方式就是使用...
    君惜丶閱讀 1,054評論 0 2
  • 探索Vue高階組件高階組件(HOC)是 React 生態(tài)系統(tǒng)的常用詞匯,React 中代碼復用的主要方式就是使用高...
    videring閱讀 10,706評論 5 30
  • Angular、React、Vue 都遵循組件開發(fā)標準,組件是構建用戶界面(UI)的基礎。 三大框架允許我們將 H...
    比較編程網(wǎng)閱讀 158評論 0 0
  • 此文基于官方文檔,里面部分例子有改動,加上了一些自己的理解 什么是組件? 組件(Component)是 Vue.j...
    陸志均閱讀 3,945評論 5 14

友情鏈接更多精彩內(nèi)容