在第8天,我將示範 Vue 3、SvelteKit 與 Angular 如何執行動態的 CSS 類別與樣式綁定。展示中包含 CSS 類別綁定與樣式綁定的範例。當一個項目已購買時,會套用刪除線(strikeout)類別。當項目被標記為高優先時,會使用 priority 類別。加入項目表單中的高優先勾選框被勾選時,字體會因 font-weight 樣式而變粗體 (bold)。
<script setup lang="ts">
import { Icon } from '@iconify/vue'
import { ref, computed } from 'vue'
type Item = { id: number; label: string; highPriority: boolean; purchased: boolean }
const items = ref<Item[]>([])
const togglePurchase = (item: Item) => {
  item.purchased = !item.purchased
}
</script>
<template>
    <ul>
      <div class="list-item" v-for="item in items" :key="item.id">
        <li
          :class="[{ priority: item.highPriority }, { strikeout: item.purchased }]"
           @click="togglePurchase(item)"
        >
          {{ item.id }} - {{ item.label }}
        </li>
      </div>
    </ul>
</template>
在 Vue 3 中,元素的 CSS 類別可以綁定到陣列或物件。在此範例中,將一個陣列綁定到 <li> 元素的類別。
{ strikeout: item.purchased } - 當 purchased 屬性為 true 時,strikeout 類別被啟用,否則停用。點擊 <li> 元素時會觸發點擊事件,togglePurchase 函式切換該屬性,導致 strikeout 類別動態套用。
{ priority: item.highPriority } - 同理,當 highPriority 屬性為 true 時,priority 類別被套用。當使用者勾選 High Priority 框並儲存時,該屬性為 true,文字會以不同顏色呈現。
若要傳入物件做類別綁定,寫法是::class="{ priority: item.highPriority, strikeout: item.purchased }"
<script lang="ts">
    import Icon from '@iconify/svelte';
    type Item = { id: number; label: string; purchased: boolean; higherPriority: boolean };
    let items = $state([] as Item[]);
    function togglePurchased(item: Item) {
        item.purchased = !item.purchased;
        newItem = '';
        newItemHigherPriority = false;
    }
</script>
<ul>
{#each items as item (item.id)}
   <div class="list-item">
       <li class={[item.purchased && 'strikeout', item.higherPriority && 'priority']}>{item.id} - {item.label}</li>
       <button class="btn" onclick={() => togglePurchased(item)} aria-label="purchase an item">
         <Icon icon={!item.purchased ? "ic:baseline-check" : "ic:baseline-close" } />
     </button>
   </div>
{/each}
</ul>
因為 Svelte 編譯器警告 <li> 不應可點擊,因此改用按鈕觸發 togglePurchase 函式切換 purchased 屬性。
如同 Vue 3,元素的 CSS 類別可綁定陣列或物件。這範例傳入陣列到 <li> 元素的類別。
{ item.purchased && 'strikeout' } - 若 purchased 為真,strikeout 類別啟用,否則停用。
{ item.highPriority && 'priority' }- 類似地,priority 類別會於 highPriority 為 true 時套用。當使用者勾選 High Priority 框儲存後,文字呈不同顏色。
若要傳入物件為類別綁定,寫法是::class="{{ priority: item.highPriority, strikeout: item.purchased }}"
import { ChangeDetectionStrategy, Component, computed, signal } from '@angular/core';
type Item = { id: number; label: string; purchased: boolean; highPriority: boolean };
@Component({
  selector: 'app-shopping-cart',
  imports: [],
  template: `
     <ul>
        @for (item of items(); track item.id) {
          @let itemClasses =
            {
              priority: item.highPriority,
              strikeout: item.purchased,
            };
          <div class="list-item">
            <li [class]="itemClasses" (click)="togglePurchase(item)">
                {{ item.id }} - {{ item.label }}
            </li>
          </div>
        }
    </ul>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ShoppingCartComponent {
  items = signal<Item[]>([]);
  newItem = signal('');
  newItemHighPriority = signal(false);
  togglePurchase(item: Item) {
    this.items.update((items) =>
      items.map((element) => (element.id === item.id ? { ...element, purchased: !element.purchased } : element))
    );
    this.newItem.set('');
    this.newItemHighPriority.set(false);
  }
}
Angular 引入了 ``@let` 語法來在模板中建立臨時變數:
@let itemClasses = {
    priority: item.highPriority,
    strikeout: item.purchased,
};
當 item.highPriority 為 true 時,itemClasses 是 { priority },當 item.purchased 為 true 時,itemClasses 是 { strikeout }。兩者都為 true 時,合併為 { priority, strikeout }。
類似屬性綁定,使用方括弧語法綁定類別:
[class]="itemClasses"`
這樣會把 itemClasses 傳入 <li> 元素的 CSS 類別。
當 newItemHighPriority 是 true 時,High Priority 勾選框的 font-weight 樣式設為粗體(bold),否則為正常(normal):
{ font-weight: newItemHighPriority ? 'bold': 'normal' } - 此樣式物件綁定給 style 屬性,生效為行內樣式。
<script setup lang="ts">
import { Icon } from '@iconify/vue'
import { ref, computed } from 'vue'
const newItemHighPriority = ref(false)
const saveItem = () => { /* 同前邏輯 */ }
</script>
<template>
  <form v-if="isEditing" @submit.prevent="saveItem">
    <label>
      <input type="checkbox" v-model="newItemHighPriority" />
      <span :style="{ 'font-weight': newItemHighPriority ? 'bold' : 'normal' }">
        High Priority</span
      >
    </label>
  </form>
</template>
當 newItemHighPriority 為 true 時,High Priority 勾選框字體粗體 (bold),否則為正常 (normal):
style:font-weight={ newItemHighPriority ? 'bold': 'normal' }
<script lang="ts">
    import Icon from '@iconify/svelte';
    let newItemHigherPriority = $state(false);
    async function handleSubmit(event: SubmitEvent) {
    /* 同前邏輯 */
    }
</script>
<form method="POST" onsubmit={handleSubmit}>
   <label>
     <input id="higherPriority" name="higherPriority" type="checkbox" bind:checked={newItemHigherPriority}
      />
      <span style:font-weight={newItemHigherPriority ? 'bold' : 'normal'}> Higher Priority</span>
   </label>
</form>
當 newItemHighPriority 信號值為 true,High Priority 勾選框的 font-weight 樣式變為粗體 (bold),否則為正常 (normal)。
使用方括弧語法綁定樣式屬性:
[style.fontWeight]="newItemHighPriority ? 'bold': 'normal'"
Angular 使用 camelcase 命名,將 font-weight 轉為 fontWeight。
import { ChangeDetectionStrategy, Component, computed, signal } from '@angular/core';
import { FormsModule } from '@angular/forms';
@Component({
  selector: 'app-shopping-cart',
  imports: [FormsModule],
  template: `
      <form (ngSubmit)="saveItem()">
        <label>
          <input type="checkbox" [(ngModel)]="newItemHighPriority" name="newItemHighPriority" />
          <span [style.fontWeight]="newItemHighPriority() ? 'bold' : 'normal'"> High Priority</span>
        </label>
      </form>
  `,
   changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ShoppingCartComponent {
  newItemHighPriority = signal(false);
  saveItem() { /* 同前邏輯 */ }
}