iT邦幫忙

2025 iThome 鐵人賽

DAY 26
0
Vue.js

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

第 25 天 - Alert 元件第 4 部分 - 更新 Alert Bar 以重新開啟已關閉的 Alert

  • 分享至 

  • xImage
  •  

在第 25 天,我擴充了 Alert Bar 元件來顯示已關閉 Alert 的按鈕。接著,使用者可以選擇重新開啟所有已關閉的 Alert,或是特定類型的 Alert。

追蹤已關閉的 Alert

Vue 3 application

<script setup lang="ts">
import { ref } from 'vue' 

const closedNotifications = defineModel<string[]>('closedNotifications', { default: [] })
</script>

AlertBar 元件中,使用 defineModel 建立一個類型為 string[]closedNotifications ref。
closedNotifications 的預設值為空清單。

針對此 ref 中的每種類型,會顯示一個按鈕,可用來開啟該警示。
當此 ref 不為空時,亦會顯示 Open all alerts 按鈕。

SvelteKit application

<script lang="ts">
    type Props = {
        ... other prop ...
        closedNotifications: string[];
    }

    let { 
        ... other bindables ...
        closedNotifications = $bindable(),
    }: Props = $props();
</script>

closedNotifications 回呼屬性 (callback prop) 新增到 Props 類型中。

接著,從 $props() 巨集中解構 closedNotifications,並使用 $bindable 巨集建立雙向綁定。


Angular 20 application

import { ChangeDetectionStrategy, Component, input, model } from '@angular/core';
import { FormsModule } from '@angular/forms';

@Component({
  selector: 'app-alert-bar',
  imports: [FormsModule],
  template: `...inline template...`,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AlertBarComponent {
  ... other models ...
  
  closedNotifications = model<string[]>([]);
}

AlertBarComponent 中,建立一個類型為 string[]closedNotifications 模型信號。closedNotifications 的預設值為空字串陣列。


用戶事件以追蹤已關閉的 Alert

Vue 3 application

在 AlertBar 元件中,有兩種用戶事件會操作 closedNotifications ref。當使用者點擊 "Open all alerts" 按鈕時,closedNotifications ref 應該設為空陣列。

當使用者點擊按鈕重新開啟特定類型的警示時,該類型必須從 closedNotifications ref 中移除。

<script setup lang="ts">
const closedNotifications = defineModel<string[]>('closedNotifications', { default: [] })

function removeNotification(type: string) {
  closedNotifications.value = closedNotifications.value.filter((t) => t !== type)
}

function clearAllNotifications() {
  closedNotifications.value = []
}

function hasClosedNotifications() {
  return closedNotifications.value.length > 0
}
</script>

removeNotification 函數會從 closedNotifications ref 中移除特定的 type
clearAllNotifications 函數會從 closedNotifications ref 中移除所有類型。

hasClosedNotifications 函數用來判斷 closedNotifications ref 是否為空。

<template>
  <div>
    <p class="mb-[0.75rem]">
      <button v-for="type in closedNotifications"
        :key="type" @click="removeNotification(type)"
      >
        <OpenIcon />{{ capitalize(type) }}
      </button>    
      <button
        v-if="hasClosedNotifications()"
        @click="clearAllNotifications">
        Open all alerts
      </button>
    </p>
  </div>
</template>

v-for 指令會遍歷 closedNotifications ref,為每個已關閉的通知顯示按鈕。當發生 click 事件時,removeNotification 函數會從該 ref 移除該 type

v-if 指令會在 closedNotifications ref 非空陣列時顯示 Open all alerts 按鈕。當發生 click 事件時,clearAllNotifications 函數會從 ref 中移除所有類型。


SvelteKit application

In the AlertBar component.

<script lang="ts">
    type Props = {
        ... omitted due to brevity ...
        closedNotifications: string[];
    }

    let { 
        ... omitted due to brevity ...
        closedNotifications = $bindable(),
    }: Props = $props();
    
    function removeNotification(type: string) {
     closedNotifications = closedNotifications.filter((t) => t !== type)
    }

    function clearAllNotifications() {
        closedNotifications = []
    }

    function hasClosedNotifications() {
        return closedNotifications.length > 0
    }
</script>

removeNotification 函數會從 closedNotifications ref 中移除特定的 type
clearAllNotifications 函數會從 closedNotifications ref 中移除所有類型。

hasClosedNotifications 函數用來判斷 closedNotifications ref 是否為空。

<div>
    <p class="mb-[0.75rem]">
        {#each closedNotifications as type (type)}
            <button
                class={getBtnClass(type) + ' mr-[0.5rem] btn'}
                onclick={() => removeNotification(type)}
            >
                <OpenIcon />{ capitalize(type) }
            </button>    
        {/each}
        {#if hasClosedNotifications()}
            <button
                class="btn btn-primary" 
                onclick={clearAllNotifications}>
                Open all alerts
            </button>
        {/if}
      </p>
</div>

在 Svelte 中,事件是以 'on' 開頭的屬性,因此點擊事件的屬性名稱為 onclick

onclick={() => removeNotification(type)} 會執行 removeNotification 函數。

onclick={clearAllNotifications} 會執行 clearAllNotifications 函數,這是一個簡寫,因為該函數不需要任何參數。

#each 會遍歷 closedNotifications 回呼屬性,為每個已關閉的通知顯示按鈕。當發生 click 事件時,removeNotification 函數會被觸發。

#ifclosedNotifications 回呼屬性非空陣列時顯示 Open all alerts 按鈕。當發生 click 事件時,clearAllNotifications 函數會被觸發。


Angular 20 application

In the AlertBarComponent

import { ChangeDetectionStrategy, Component, input, model } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { capitalize } from '../capitalize';
import { OpenIconComponent } from '../icons/icon.component';

@Component({
  selector: 'app-alert-bar',
  imports: [FormsModule, OpenIconComponent],
  template: `...inline template...`,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AlertBarComponent {
  closedNotifications = model<string[]>([]);

  removeNotification(type: string) {
    this.closedNotifications.update((prev) => prev.filter((t) => t !== type));
  }
    
  clearAllNotifications() {
    this.closedNotifications.set([])
  }
    
  hasCloseButtonChanged() {
    return this.closedNotifications().length > 0;
  }
}

removeNotification 方法呼叫 closedNotifications 模型信號的 update 方法,以移除特定的 type

clearAllNotifications 方法呼叫 closedNotifications 模型信號的 set 方法,以移除所有類型。

hasClosedNotifications 方法用來判斷 closedNotifications 模型信號是否為空。

<div>
    <p class="mb-[0.75rem]">
      @for (type of closedNotifications(); track type) {
        <button (click)="removeNotification(type)">
          <app-open-icon />{{ capitalize(type) }}
        </button>
      }
      @if (hasClosedNotifications()) { 
        <button (click)="closedNotifications.set([])">
          Open all alerts
        </button>
      }
    </p>
</div>

在 Angular 中,事件透過發射(emit)產生,並使用香蕉語法 (event) 將事件發射給父元件。

(click)="removeNotification(type)" 會執行 removeNotification 方法。

(click)="clearAllNotifications" 會執行 clearAllNotifications 方法。

@each 會遍歷 closedNotifications 模型信號,為每個已關閉的通知顯示按鈕。 當發生 click 事件時,會觸發 removeNotification 方法。

@ifclosedNotifications 模型信號非空陣列時顯示 Open all alerts 按鈕。 當發生 click 事件時,會觸發 clearAllNotifications 方法。


AlertList 元件的雙向綁定

Vue 3 application

<script setup lang="ts">
import type { AlertType } from '@/types/alert-type';
import { computed, ref } from 'vue';
import Alert from './Alert.vue';
import AlertBar from './AlertBar.vue';

const closedNotifications = ref<string[]>([])

const alerts = computed(() => props.alerts.filter((alert) => 
  !closedNotifications.value.includes(alert.type))
)

function handleClosed(type: string) {
  closedNotifications.value.push(type)
}
</script>

closedNotifications ref 綁定到 AlertBar 元件的 closedNotifications ref。

alerts 計算型 ref 是一個已開啟警示的陣列。當 AlertBar 元件從 closedNotification 中移除任何項目時,alerts ref 會重新計算要顯示的警示。

handleClosed 函數會將已關閉警示的類型新增到 closedNotifications ref 中。此函數執行後,alerts ref 會重新計算以取得新值。

<template>
  <AlertBar
    v-model:closedNotifications="closedNotifications"
  />
</template>

v-model 指令將 closedNotifications ref 綁定到 AlertBar 元件的 closedNotifications ref。 當 AlertBar 元件中的 closedNotifications 更新時,AlertList 元件中的 closedNotifications 也會更新。v-for 指令會遍歷 alerts 計算型 ref,顯示已開啟的警示。

<template>
  <Alert v-for="{ type, message } in alerts"
    :key="type"
    :type="type"
    @closed="handleClosed">
    {{  message }}
  </Alert>
</template>

v-for 指令會遍歷 alerts 計算型 ref,顯示已開啟的警示。


SvelteKit application

<script lang="ts">
	import AlertBar from './alert-bar.svelte';
    import Alert from './alert.svelte';

    let closedNotifications = $state<string[]>([]);

    let filteredNotifications = $derived.by(() => 
        alerts.filter(alert => !closedNotifications.includes(alert.type))
    );

    function notifyClosed(type: string) {
        console.log(`Alert of type ${type} closed`);
        closedNotifications.push(type);
    }
</script>

closedNotifications 變數綁定到 AlertBar 元件的 closedNotifications 變數。
filteredNotifications 衍生變數是一個已開啟警示的陣列。當 AlertBar 元件從 closedNotifications 中移除任何項目時,filteredNotifications 衍生變數會重新計算要顯示的警示。

handleClosed 函數會將已關閉警示的類型新增到 closedNotifications 變數中。該函數執行後,filteredNotifications 衍生變數會重新計算以取得新值。

<AlertBar 
    bind:closedNotifications={closedNotifications}
/>

{#each filteredNotifications as alert (alert.type) } 
    <Alert {notifyClosed} />
{/each}

bind:closedNotifications={closedNotifications} 表示 AlertList 元件會監聽 AlertBar 元件的 closedNotifications prop。

#each 會遍歷 filteredNotifications 衍生變數,顯示已開啟的警示。


Angular 20 application

import { ChangeDetectionStrategy, Component, computed, input, signal } from '@angular/core';
import { AlertType } from '../alert.type';
import { AlertComponent } from '../alert/alert.component';
import { AlertBarComponent } from '../alert-bar/alert-bar.component';

@Component({
  selector: 'app-alert-list',
  imports: [AlertComponent, AlertBarComponent],
  template: `...inline template...`,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AlertListComponent {
  closedNotifications = signal<string[]>([]);

  filteredAlerts = computed(() => 
    this.alerts().filter(alert => !this.closedNotifications().includes(alert.type))
  ); 

  handleCloseNotification(type: string) {
    this.closedNotifications.update((prev) => ([...prev, type ]));
  }
}

closedNotifications 信號綁定到 AlertBar 元件的 closedNotifications 模型信號。

filteredAlerts 計算型信號是一個已開啟警示的陣列。當 AlertBar 元件從 closedNotifications 中移除任何項目時,filteredAlerts 計算型信號會重新計算要顯示的警示。

handleCloseNotification 方法會將已關閉警示的類型新增到 closedNotifications 信號中。該方法執行後,filteredAlerts 會重新計算以取得新值。

<app-alert-bar 
    [(closedNotifications)]="closedNotifications"
/>
@for (alert of filteredAlerts(); track alert.type) {
  <app-alert [type]="alert.type" 
    [alertConfig]="alertConfig()"(closeNotification)="handleCloseNotification($event)">
    {{ alert.message }}
  </app-alert>
}

[(closedNotifications)]="closedNotifications"
/> 表示 AlertList 元件會監聽 AlertBar 元件的 closedNotifications 模型信號。

@for 會遍歷 filteredAlerts 計算型信號,顯示已開啟的警示。

現在,使用者可以點擊 AlertBar 元件中的按鈕來開啟已關閉的警示。


Github Repositories

Github Pages

資源


上一篇
第 24 天 - Alert 元件 第 3 部分 - 新增用於更換樣式的 Alert Bar
下一篇
第26天 - 從 AlertBar 中抽取邏輯和元件
系列文
作為 Angular 專家探索 Vue 3 和 Svelte 530
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言