在第23天,我動態渲染警示(alert)的 SVG 圖示,因為動態渲染更易於維護(maintainable)、擴展(scalable)及高效(efficient)。
我為 SVG 圖示建立組件(components),並根據警示類型(alert type)渲染相應的圖示組件。
| Framework | Method | 
|---|---|
| Vue 3 | :is attribute with the | 
| Svelte 5 | Dynamic component is capitalized | 
| Angular | ngComponentOutlet structural directive enables dynamic rendering of the components | 
在 src 目錄下建立一個名為 icons 的資料夾(directory)。
<template>
    <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="h-6 w-6 shrink-0 stroke-current">
      <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
    </svg>
</template>  
<template>
  <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 shrink-0 stroke-current" fill="none" viewBox="0 0 24 24">
    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
  </svg>
</template>
<template>
    <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 shrink-0 stroke-current" fill="none" viewBox="0 0 24 24">
      <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
    </svg>
</template> 
<template>
    <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 shrink-0 stroke-current" fill="none" viewBox="0 0 24 24">
        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
    </svg>
</template>  
在 .vue 檔案中,SVG 圖示定義在 <template> 元素中。
Create an icons directory under src/lib
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="h-6 w-6 shrink-0 stroke-current">
    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg> 
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 shrink-0 stroke-current" fill="none" viewBox="0 0 24 24">
    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 shrink-0 stroke-current" fill="none" viewBox="0 0 24 24">
    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 shrink-0 stroke-current" fill="none" viewBox="0 0 24 24">
    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
在 .svelte 檔案中,SVG 圖示是在檔案內定義的。
在 src/app 目錄下建立一個名為 icons 的資料夾(directory)。
@Component({
  selector: 'app-info-icon',
  template: `
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="h-6 w-6 shrink-0 stroke-current">
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class InfoIconComponent {}
@Component({
  selector: 'app-success-icon',
  template: `
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 shrink-0 stroke-current" fill="none" viewBox="0 0 24 24">
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SuccessIconComponent {}
@Component({
  selector: 'app-warning-icon',
  template: `
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 shrink-0 stroke-current" fill="none" viewBox="0 0 24 24">
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class WarningIconComponent {}
@Component({
  selector: 'app-error-icon',
  template: `
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 shrink-0 stroke-current" fill="none" viewBox="0 0 24 24">
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ErrorIconComponent {}
在組件(component)類別中,由於代碼簡短,SVG 圖示定義為內嵌模板(inline template)。可以在 @Component 裝飾器(decorator)的 template 屬性中看到。
目標是從 Alert 組件(component)中刪除條件分支。
<script setup lang="ts"> 
    import InfoIcon from '@/icons/InfoIcon.vue'
    import SuccessIcon from '@/icons/SuccessIcon.vue'
    import WarningIcon from '@/icons/WarningIcon.vue'
    import ErrorIcon from '@/icons/ErrorIcon.vue'
    const icon = computed(() => ({
        info: InfoIcon,
        warning: WarningIcon,
        error: ErrorIcon,
        success: SuccessIcon,
    }[type]))
</script>
將 Icon 組件匯入到 Alert 組件(component)。然後,定義一個 icon 計算引用(computed ref),用以推導要顯示的圖示。字典根據 type 屬性(prop)來索引,以決定適當的圖示。
<component :is="icon" />    
v-if 和 v-if-else 被一行代碼所取代。
<script lang="ts">
    import InfoIcon from '$lib/icons/info-icon.svelte'
    import ErrorIcon from '$lib/icons/error-icon.svelte'
    import SuccessIcon from '$lib/icons/success-icon.svelte'
    import WarningIcon from '$lib/icons/warning-icon.svelte'
    
    const Icon = $derived.by(() => ({
        info: InfoIcon,
        success: SuccessIcon,
        warning: WarningIcon,
        error: ErrorIcon,
    }[alert.type]));
</script>
將 Icon 組件匯入到 Alert 組件(component)。然後,定義一個顯示圖示的 Icon 衍生 rune(derived rune)。字典根據 alert.type 屬性(prop)來索引,以決定適當的圖示。
   <Icon ></Icon> 
if-else-if 控制流程被 <Icon></Icon> 所取代。動態組件的名稱必須大寫,因此,衍生出的 rune 是 Icon。
import { NgComponentOutlet } from '@angular/common';
import { ErrorIconComponent, InfoIconComponent, SuccessIconComponent, WarningIconComponent } from '../icons/icon.component';
@Component({
  selector: 'app-alert',
  imports: [NgComponentOutlet],
  template: `
    <ng-container [ngComponentOutlet]="icon()" />
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AlertComponent {
  icon = computed(() => {
    return {
        info: InfoIconComponent,
        warning: WarningIconComponent,
        error: ErrorIconComponent,
        success: SuccessIconComponent,
    }[this.type()];
  });
}
The if-else-if control flow is replaced with <ng-container [ngComponentOutlet]="icon()" />.
將 Icon 組件匯入到 AlertComponenet。然後,定義一個 icon 計算信號(computed signal),用以推導要顯示的圖示。字典根據 type 輸入信號 (input signal) 來索引,以決定適當的圖示。
if-else-if 控制流程被 <ng-container [ngComponentOutlet]="icon()" /> 所取代。
模板已整理為一行代碼來渲染動態組件。
我們已成功在 Vue、Svelte 和 Angular 框架中渲染警示中的圖標組件。