iT邦幫忙

2025 iThome 鐵人賽

DAY 11
0
Vue.js

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

第10天 - Vue 3、Svelte 5 和 Angular 的響應式介紹

  • 分享至 

  • xImage
  •  

在第10天,我們終於學習了一個關鍵的響應式概念:從現有狀態衍生新狀態。在 Vue 3 與 Angular 中,使用 computed 函數來從其他狀態創建只讀的響應式狀態。這些狀態可以是只讀或可寫的狀態。在 Svelte 5 中,衍生狀態透過 $derived$derived.by rune 創建。

以下是三個衍生狀態的示例:

  1. 建立一個從最新到最舊顯示的項目列表
  2. 衍生已購買項目的數量
  3. 衍生已購買項目數量的顯示文本,若數量為1,則單位為單數,否則為複數

範例一:構建反轉的項目列表

Vue 3 範例

首先,從 vue 中導入 computedreverse_items 是透過複製 items ref 並反轉來建立的 computed

在模板中,使用 v-if 指令檢查 reversed_items 的長度是否大於零。條件為真時,使用 v-for 指令遍歷 reversed_items,由新至舊顯示項目。

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

const items = ref<Item[]>([])
const reverse_items = computed(() => [...items.value].reverse())

</script>
<template v-if="reversed_items.length > 0">
    <ul>
      <div class="list-item" v-for="item in reverse_items" :key="item.id">
        <li>{{ item.id }} - {{ item.label }}
        </li>
      </div>
    </ul>
</template>

SvelteKit 範例

使用 $derived rune 取得反轉 items 列表並指派給 reversed_items。模板使用 #if 檢查 reversed_items 非空,然後用 #each 由新至舊遍歷項目。

<script lang="ts">
    let items = $state([] as Item[]);
    let reversed_items = $derived([...items].reverse());
</script>
{#if reversed_items.length > 0}
<ul>
   {#each reversed_items as item (item.id)}
       <div class="list-item">
          <li>{item.id} - {item.label}</li>
       </div>
    {/each}
</ul>
{/if}

Angular 19 範例

import { ChangeDetectionStrategy, Component, computed, signal } from '@angular/core';

@Component({
  selector: 'app-shopping-cart',
  imports: [FormsModule, NgIcon],
  template: `
    @if (reverse_items().length > 0) {
      <ul>
        @for (item of reverse_items(); track item.id) {
          <div class="list-item">
            <li>{{ item.id }} - {{ item.label }}</li>
          </div>
        }
      </ul>
    }
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ShoppingCartComponent {
  items = signal<Item[]>([]);
  reverse_items = computed(() => [...this.items()].reverse());
}

@angular/core 導入 computedreverse_items 的定義與 Vue 3 相同,模板透過 @if 判斷 reverse_items 是否有元素,條件為真時,以 @for 逐一由新至舊顯示。

範例二:衍生已購買項目的數量

Vue 3 範例

計算已購買商品的數量,可以通過 computed 中使用 Array.reduce 來完成,統計 purchased 屬性為 true 的項目數。

<script setup lang="ts">
import { Icon } from '@iconify/vue'
import { ref, computed } from 'vue'

const items = ref<Item[]>([])

const num_items_purchased = computed(() =>
  items.value.reduce((acc, item) => acc + (item.purchased ? 1 : 0), 0),
)

SvelteKit 範例

<script lang="ts">
    let items = $state([] as Item[]);
    let num_items_purchased = $derived(
        items.reduce((acc, item) => acc + (item.purchased ? 1 : 0), 0)
    );
</script>

num_items_purchased rune 是透過在 $derived rune 中調用 Array.reduce 來衍生出來的。"

Angular 19 範例

import { ChangeDetectionStrategy, Component, computed, signal } from '@angular/core';

@Component({
  selector: 'app-shopping-cart',
  template: `
     ...
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ShoppingCartComponent {
  items = signal<Item[]>([]);

  num_items_purchased = computed(() => this.items().reduce((acc, item) => acc + (item.purchased ? 1 : 0), 0));
}

num_items_purchased 是一個 computed signal,用來表示已購買商品的數量。透過迭代 items signal,計算出 purchased 屬性為 true 的項目數量。

範例三:衍生已購買項目數的顯示標籤

Vue 3 範例

num_items_purchased_label 是從 num_items_purchased 衍生出來的顯示字串。

<script setup lang="ts">
import { Icon } from '@iconify/vue'
import { ref, computed } from 'vue'

const items = ref<Item[]>([])

const num_items_purchased = computed(() =>
  items.value.reduce((acc, item) => acc + (item.purchased ? 1 : 0), 0),
)

const num_items_purchased_label = computed(() => {
  const unit = num_items_purchased.value === 1 ? 'item' : 'items'
  return `${num_items_purchased.value} ${unit} purchased`
})
</script>

num_items_purchased 等於 1 時,顯示文字為 "num_items_purchased.value item purchased"。當 num_items_purchased 大於 1 時,顯示文字為 "num_items_purchased.value items purchased"。

<div class="header">
      <template v-if="num_items_purchased > 0 && num_items_purchased < items.length">
        {{ num_items_purchased_label }}</template
      >
      <template v-else-if="num_items_purchased === 0">
          You have not purchased any items yet.
      </template>
      <template v-else>You have bought everything in the shopping cart.</template>
</div>

num_items_purchased 在 1 和小於總商品數之間時,模板會顯示 num_items_purchased_label 的值。
num_items_purchased 是 0 時,模板會顯示 "You have not purchased any item yet."。
num_items_purchased 等於總商品數時,模板會顯示 "You have bought everything in the shopping cart."。

SvelteKit 範例

num_items_purchased_label 無法用 $derived,因此我們使用 $derived.by 來建立一個衍生的 rune。

<script lang="ts">
    let items = $state([] as Item[]);
    let num_items_purchased = $derived(
        items.reduce((acc, item) => acc + (item.purchased ? 1 : 0), 0)
    );
    let num_items_purchased_label = $derived.by(() => {
        const unit = num_items_purchased > 1 ? 'items' : 'item';
        return `${num_items_purchased} ${unit} purchased`;
    });
</script>
{#if num_items_purchased > 0 && num_items_purchased < items.length}
    {num_items_purchased_label}
{:else if num_items_purchased == 0}
    You have not purchased any items yet.
{:else}
    You have bought everything in the shopping cart.
{/if}

類似於 Vue 3 應用程式,if-else-if-else 控制流程用於顯示 num_items_purchased_label rune 以及靜態字串。

Angular 19 範例

import { ChangeDetectionStrategy, Component, computed, signal } from '@angular/core';

@Component({
  selector: 'app-shopping-cart',
  template: `
      <div class="header">
        @let num = num_items_purchased();
        @if (num > 0 && num < items().length) {
          {{ num_items_purchased_label() }}
        } @else if (num === 0) {
          You have not purchased any items yet.
        } @else {
          You have bought everything in the shopping cart.
        }
      </div>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ShoppingCartComponent {
  items = signal<Item[]>([]);

  num_items_purchased = computed(() => this.items().reduce((acc, item) => acc + (item.purchased ? 1 : 0), 0));

  num_items_purchased_label = computed(() => {
    const unit = this.num_items_purchased() === 1 ? 'item' : 'items';
    return `${this.num_items_purchased()} ${unit} purchased`;
  });
}

num_items_purchased 值在控制流程中出現了三次。因此,我們將其重構為一個 num 變數。當 num 在 1 和小於總商品數之間時,會顯示 num_items_purchased_label 的值。
num 為 0 時,顯示 "You have not purchased any item yet"。
num 等於總項目數時,顯示 "You have bought everything in the shopping cart."。

資源

Github Repositories

Github Pages


上一篇
第 9 天 - 在 Vue 3、Svelte 5 和 Angular 中屬性綁定
下一篇
第11天 將 Vue 3、Svelte 5 和 Angular 應用程式部署到 Github Pages
系列文
作為 Angular 專家探索 Vue 3 和 Svelte 517
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言