在第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 框架中渲染警示中的圖標組件。