在第 6 天,我將說明 Vue 3、SvelteKit 與 Angular 如何在購物車元件中回應 HTML 事件。
在購物車元件中,我們會在表單送出事件時,將新項目加入項目清單;同時,當刪除按鈕被點擊時,該項目會從清單中移除。
<script> 區塊中新增 saveItem
方法,用以將新項目加入 items
的 ref
陣列。由於 items
、newItem
、newItemHighPriority
都是 Vue 的 ref,因此存取它們的值時需要使用 .value
。新的項目包含 newItem
與 newItemHighPriority
的值,然後被加入陣列中。
<script setup lang="ts">
import { Icon } from "@iconify/vue";
import { ref } from 'vue';
const items = ref<Item[]>([])
const newItem = ref('')
const newItemHighPriority = ref(false)
const saveItem = () => {
items.value.push({
id: items.value.length + 1,
label: newItem.value,
highPriority: newItemHighPriority.value,
purchased: false,
})
newItem.value = ''
newItemHighPriority.value = false
}
</script>
<template>
<form @submit.prevent="saveItem">
<input v-model.trim="newItem" />
<label>
<input type="checkbox" v-model="newItemHighPriority" />
<span>High Priority</span>
</label>
<button>Save Item</button>
</form>
</template>
@submit.prevent
監聽表單送出事件並呼叫 saveItem
,同時透過 preventDefault
避免頁面重新載入。v-model
雙向綁定 newItem
與輸入欄位、newItemHighPriority
與勾選框雙向綁定。
在 <script> 中定義 saveItem
方法,將新項目加入狀態陣列中。新項目包含 newItem
與 newItemHighPriority
的值。
<script lang="ts">
import Icon from '@iconify/svelte';
type Item = { id: number; label: string; purchased: boolean; higherPriority: boolean };
let newItem = $state('');
let newItemHigherPriority = $state(false);
let items = $state([] as Item[]);
function saveItem() {
if (newItem) {
items.push({
id: items.length + 1,
label: newItem,
purchased: false,
higherPriority: newItemHigherPriority
});
newItem = '';
newItemHigherPriority = false;
}
}
async function handleSubmit(event: SubmitEvent) {
event.preventDefault();
saveItem();
}
</script>
<form method="POST" onsubmit={handleSubmit}>
<input id="newItem" name="newItem" type="text" bind:value={newItem} />
<label>
<input id="higherPriority" name="higherPriority" type="checkbox" bind:checked={newItemHigherPriority} />
<span> Higher Priority</span>
</label>
<button class="btn btn-primary">Save Item</button>
</form>
Svelte 5 以 on
開頭的事件處理器名稱偵測並處理 DOM 事件。表單使用 onSubmit
執行 handleSubmit
,該函式使用 preventDefault
避免頁面重新載入,並呼叫 saveItem
新增項目。
Angular 信號 (signal) 的變異處理方式與 Vue 3、Svelte 不同。當新項目加入陣列時,items
signal 不會自動更新,必須更新陣列的引用 (array reference)。Angular 提供 update
方法使用回呼函式 (callback function) 從前一狀態建立新的陣列。
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { NgIcon, provideIcons } from '@ng-icons/core';
import { matRemove } from '@ng-icons/material-icons/baseline';
type Item = { id: number; label: string; purchased: boolean; highPriority: boolean };
@Component({
selector: 'app-shopping-cart',
imports: [FormsModule, NgIcon],
viewProviders: [provideIcons({ matRemove })],
template: `
<form class="add-item-form" (ngSubmit)="saveItem()">
<input type="text" name="newItem" [(ngModel)]="newItem" />
<label>
<input type="checkbox" [(ngModel)]="newItemHighPriority" name="newItemHighPriority" />
<span> High Priority</span>
</label>
<button type="submit">Save Item</button>
</form>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ShoppingCartComponent {
items = signal<Item[]>([]);
newItem = signal('');
newItemHighPriority = signal(false);
saveItem() {
if (!this.newItem()) return;
const id = this.items().length + 1;
this.items.update(items => [
...items,
{
id,
label: this.newItem(),
purchased: false,
highPriority: this.newItemHighPriority()
}
]);
this.newItem.set('');
this.newItemHighPriority.set(false);
}
}
Angular 的 ngSubmit
事件會處理表單送出,且會自動呼叫 preventDefault
避免頁面重載。表單使用 banana syntax (ngSubmit)="saveItem()"
綁定事件處理函式。
deleteItem
函式利用 filter
過濾 items.value
陣列以去除指定 ID
的項目,並將結果重新指定給 items.value。
<script setup lang="ts">
import { Icon } from "@iconify/vue";
import { ref } from 'vue';
const items = ref<Item[]>([])
const newItem = ref('')
const newItemHighPriority = ref(false)
const deleteItem = (id: number) => {
items.value = items.value.filter(item => item.id !== id)
}
</script>
<template>
<!-- 先前的表單 -->
<ul>
<div v-for="item in items" :key="item.id">
<li>{{ item.id }} - {{ item.label }}</li>
<button aria-label="Delete" @click="deleteItem(item.id)">
<Icon icon="ic:baseline-remove" />
</button>
</div>
</ul>
</template>
按鈕的 @click
事件會觸發 deleteItem
,刪除指定項目。
同樣用 filter
過濾 items
陣列以刪除指定 ID
項目。
<script lang="ts">
import Icon from '@iconify/svelte';
type Item = { id: number; label: string; purchased: boolean; higherPriority: boolean };
let newItem = $state('');
let newItemHigherPriority = $state(false);
let items = $state([] as Item[]);
function deleteItem(id: number) {
items = items.filter(item => item.id !== id);
}
</script>
<ul>
{#each items as item (item.id)}
<li>{item.id} - {item.label}</li>
<button onclick={() => deleteItem(item.id)} aria-label="delete an item">
<Icon icon="ic:baseline-remove" />
</button>
{/each}
</ul>
Svelte 5 使用 onClick
綁定點擊事件並執行刪除。
使用 update
方法與 Array.filter
創建新陣列,刪除指定 ID
項目。
type Item = { id: number; label: string; purchased: boolean; highPriority: boolean };
@Component({
selector: 'app-shopping-cart',
imports: [FormsModule, NgIcon],
viewProviders: [provideIcons({ matRemove })],
template: `
<ul>
@for (item of items(); track item.id) {
<li [class]="itemClasses">
{{ item.id }} - {{ item.label }}
</li>
<button aria-label="Delete" (click)="deleteItem(item.id)">
<ng-icon name="matRemove"></ng-icon>
</button>
}
</ul>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ShoppingCartComponent {
items = signal<Item[]>([]);
newItem = signal('');
newItemHighPriority = signal(false);
deleteItem(id: number) {
this.items.update(items => items.filter(item => item.id !== id));
}
}
Angular 使用 (click)="deleteItem(item.id)"
綁定點擊事件。
這樣我們成功讓購物車元件在送出表單時新增項目,並在點擊按鈕時刪除項目。