iT邦幫忙

2025 iThome 鐵人賽

DAY 17
0
Vue.js

作為 Angular 專家探索 Vue 3 和 Svelte 5系列 第 17

第16天 - 使用元件事件選擇咖啡方案

  • 分享至 

  • xImage
  •  

在第16天,當選擇了一個咖啡方案 (coffee plan) 時,我會在該方案周圍加上邊框。其他咖啡方案則變成非活躍狀態,並移除邊框。CoffeePlan 組件會發出目前啟用方案的名稱給 PlanPicker 組件,以便通知其他咖啡方案移除邊框。

定義組件事件以發送活躍方案 (Active Plan)

  • Vue 3 application
const emit = defineEmits<{
  (e: 'selectedPlan', name: string): void
}>()

function selectPlan() {
  emit('selectedPlan', props.name)
}

CoffeePlan 組件定義了一個自訂事件,將名稱發送給 PlanPicker 組件。

selectedPlan 函數使用 selectedPlan 事件將方案名稱發送給父組件。PlanPicker 組件接收活躍的方案,並可通知其他方案它們不再是活躍狀態

  • SvelteKit application
<script lang="ts">
	interface Props {
        name: string;
		selectedPlan: (name: string) => void;
	}

	let { name = 'Default Plan', selectedPlan }: Props = $props();

	const handleSelectPlan = () => selectedPlan(name);
</script>

CoffeePlan 組件從 $props() 中取得 selectedPlan 函數。

PlanPicker 組件必須提供給 CoffeePlan 組件一個 prop callback,以供 handleSelectPlan 函數調用。

handleSelection 會以方案名稱呼叫 selectedPlan 函數。

  • Angular 19 application
@Component({
  ...
})
export class CoffeePlanComponent {
  name = input('Default Plan');
  selectedPlan = output<string>();

  selectPlan() {
    this.selectedPlan.emit(this.name());
  }
}

CoffeePlanComponent 宣告了一個 selectedPlan 輸出,用來將咖啡名稱發送給 PlanPickerComponentname 是一個字串類型的信號輸入。

當呼叫 selectPlan 方法時,selectedPlan 會將 name getter 的結果輸出給 PlanPickerComponent

在 CoffeePlan 組件中新增 selected prop

  • Vue 3 application
<script setup lang="ts">
const props = defineProps({
  selected: {
    type: Boolean,
    default: false,
  },
})
</script>

selected 新增到 CoffeePlanprops 中。當 selectedtrue 時,方案為活躍狀態並帶有邊框;當 selected 為 false 時,則移除邊框。

<template>
  <div class="plan" @click="selectPlan" :class="{ 'active-plan': selected }">
    <div class="description">
      <span class="title"> {{ name }} </span>
    </div>
  </div>
</template>

div 元素動態綁定到 active-plan 類別 (class)。當 selected 為 true 時,該 CSS 類別會被啟用。否則,類別會被移除。

  • SvelteKit application
<script lang="ts">
	interface Props {
		name: string;
		selectedPlan: (name: string) => void;
		selected: boolean;
	}

	let { name = 'Default Plan', selectedPlan, selected }: Props = $props();

    const handleSelectPlan = () => selectedPlan(name);
</script>

同樣地,selected 標誌是從 $props 中提取的。此外,Props 介面中有一個類型為 booleanselected 屬性。

<div onclick={handleSelectPlan} class={['plan', selected && 'active-plan']}>
	<div class="description">
		<span class="title"> {name} </span>
	</div>
</div>

selected 為 true 時,active-plan 會成為類別列表 (class list) 的一部分。否則,類別列表 (class list) 中不會包含該類別。

  • Angular 19 application
import { ChangeDetectionStrategy, Component, input, output } from '@angular/core';

@Component({
  selector: 'app-coffee-plan',
  template: `
    <div class="plan" (click)="selectPlan()" [class]="{ 'active-plan': selected() }">
      <div class="description">
        <span class="title"> {{ name() }} </span>
      </div>
    </div>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CoffeePlanComponent {
  name = input('Default Plan');
  selected = input(false);

  selectedPlan = output<string>();
}

selected 是一個初始為 false 的信號輸入。

div 元素的 class 屬性綁定到一個物件。當 selected 的 getter 函式返回 true 時,active-plan 類別 (class) 會被啟用。否則,該類別 (class) 會從元素中移除,邊框也不會顯示。

PlanPicker 通知其他 CoffeePlan 取消選取

  • Vue 3 application
// PlanPicker

<script setup lang="ts">
const selectedPlan = ref('')
function handleSelectPlan(name: string) {
  selectedPlan.value = name
}

function isSelected(plan: string) {
  return selectedPlan.value === plan
}
</script>

<template>
  <div class="plans">
    <CoffeePlan
      v-for="plan in plans"
      :key="plan"
      :name="plan"
      :selected="isSelected(plan)"
      @selectedPlan="handleSelectPlan"
    >
    </CoffeePlan>
  </div>  
</template>

CoffeePlan 發出 selectedPlan 事件時,handleSelectedPlan 會更新 selectedPlan ref。isSelected 函式用來判斷是否有選擇咖啡方案 (coffee plan)。接著,該函式會將結果指派給 CoffeePlan 組件的 selected 輸入。當方案被選中時,CSS 會為其添加邊框,否則咖啡方案不會顯示邊框。

  • SvelteKit application
<script lang="ts">
    let selectedCoffeePlan = $state('');
    const selectedPlan = (name: string) => (selectedCoffeePlan = name);
    const isSelected = (plan: string) => selectedCoffeePlan === plan;
</script>

<div class="plans">
{#each plans as plan (plan)}
    <CoffeePlan name={plan} {selectedPlan} selected={isSelected(plan)} />
{/each}
</div>

CoffeePlan 發出 selectedPlan 事件時,selectedPlan 會更新 selectedCoffeePlan rune。isSelected 函式用來判斷是否有選擇咖啡方案。接著,該函式會將結果指派給 CoffeePlan 組件的 selected 輸入。當方案被選中時,CSS 會為其添加邊框,否則咖啡方案不會顯示邊框。

  • Angular application

@Component({
  selector: 'app-plan-picker',
})
export class PlanPickerComponent {
  selectedPlan = signal('');

  handleSelectPlan(name: string) {
    this.selectedPlan.set(name);
  }

  isPlanSelected(planName: string): boolean {
    return this.selectedPlan() === planName;
  }
}
<div class="plans">
    @for (plan of plans(); track plan) {
        <app-coffee-plan
            [name]="plan"                           
            (selectedPlan)="handleSelectPlan($event)"
            [selected]="isPlanSelected(plan)"
        />
      }
</div>

PlanPickerComponent 發出 selectedPlan 事件時,handleSelectPlan 方法會更新 selectedPlan 信號。isPlanSelected 方法用來判斷是否有選擇咖啡方案。接著,該方法會將結果指派給 CoffeePlan 組件的 selected 輸入。當方案被選中時,CSS 會為其添加邊框,否則咖啡方案不會顯示邊框。

結論

我們成功地新增了新的 prop 和組件事件 (component event),用於在 CoffeePlan 組件和 PlanPicker 組件之間進行溝通。PlanPickerselected 的值中衍生新的值,並將其傳遞給其他 CoffeePlan 組件,以動態啟用或停用 CSS 類別。

資源

Github Repositories


上一篇
第 15 天- Add a Coffee Plan Form
下一篇
第 17 天 - 在 HTML 模板中渲染動態內容
系列文
作為 Angular 專家探索 Vue 3 和 Svelte 519
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言