iT邦幫忙

2025 iThome 鐵人賽

DAY 20
0
Vue.js

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

第 19 天 - Github 卡片專案 第二部分 - 元件組合

  • 分享至 

  • xImage
  •  

示範中使用卡片佈局 (card layout) 顯示 Github 使用者。GithubProfileList 會迭代一個使用者名稱的清單,並在 GithubProfileCard 中顯示每位使用者。

建立 GithubProfileCard 元件

Vue 3 application

// components/GithubProfileCard.vue

<script setup lang="ts">
import { useGithubProfile } from '@/composables/useGithubProfile.ts'

type Prop = {
    username: string
}

const { username } = defineProps<Prop>()
</script>

GithubProfileCard 預期接收一個 username 的 prop,該 prop 可指派給 useGithubProfile composable 中的 username ref。該專案是使用 TypeScript 撰寫,因此 defineProps 巨集(macro)可以接受一個 prop 類型。


<script setup lang="ts">
const { username: name, profile, error } = useGithubProfile()

name.value = username
</script>

useGithubProfile composable 中解構(destructure)usernameprofileerror refs。username 被別名為 name,以避免與 username prop 發生衝突。

name 這個 ref 更新時,composable 會取得 Github 個人資料,並將結果存放在 profile ref 中。

<template>
    <div v-if="profile">
        <p>Username: {{ profile.login }}</p>
        <p>Name: {{ profile.name }}</p>
        <p>Bio: {{ profile.bio || 'N/A' }}</p>
    </div>
    <div v-else-if="error">
        Error: {{ error }}
    </div>
</template>

v-if 指令會檢查 profile 這個 ref 的值。當 profile 被定義時,模板會顯示登入帳號(login)、名稱(name)和個人簡介(bio)。

v-else-if 指令則會檢查 error 的值,當 error 不為空白時,會顯示錯誤訊息。

SvelteKit application

// github-profile.type.ts

export type GithubProfileItem = { 
    key: number; 
    profile?: GithubProfile; 
    error?: string 
}

GithubProfileItem 類型由 profileerror 屬性組成。

// github-profile-card.svelte

<script lang="ts">
    import type { GithubProfileItem } from './github-profile-item.type';

    type Props = {
        profile: GithubProfileItem
    };

    const { profile: result }: Props = $props();
    const { profile, error } = result;
</script>

在這個元件中,$props() 被轉型(cast)為 Props 類型。接著從 prop 中解構出 profileerror

{#if profile}
    <div>
        <p>Username: {profile.login}</p>
        <p>Name: {profile.name}</p>
        <p>Bio: {profile.bio || 'N/A'}</p>
    </div>
{:else if error}
    <div>
        <p>Error: {error}</p>
    </div>
{/if}

在模板中,if-else-if 控制流程語法會檢查 profileerror 兩者的值。當 profile 被定義時,if 分支會顯示 Github 個人資料;而當 error 不為空白時,else-if 分支會顯示錯誤訊息。

Angular 20 application

// github-profile-card.component.ts

import { httpResource } from '@angular/common/http';
import { ChangeDetectionStrategy, Component, computed, input } from '@angular/core';
import { GithubProfile } from '../types/github-profile.type';

@Component({
    selector: 'app-github-profile-card',
    templateUrl: './github-profile-card.component.html',
    changeDetection: ChangeDetectionStrategy.OnPush,
})
export class GithubProfileCardComponent {
    username = input.required<string>();

    profileResource = httpResource<GithubProfile>(() => this.username() ? { 
        url: `https://api.github.com/users/${this.username()}`,
        method: 'GET',
        headers: {
            Authorization: `Bearer ${GITHUB_TOKEN}`
        }
    }: undefined, {
        equal: (a, b) => a?.login === b?.login,
    });

    profile = computed(() => this.profileResource.hasValue() ? this.profileResource.value() : undefined);

    error = computed(() => this.profileResource.error()?.message || '');
}

元件有一個必須的信號輸入 (required signal input),名為 username

username 輸入 (input) 改變時,profileResource 會以反應式方式建立一個 HttpResourceRequest

{ 
    url: `https://api.github.com/users/${this.username()}`,
    method: 'GET',
    headers: {
        Authorization: `Bearer ${GITHUB_TOKEN}`
    }
}

該請求包含 Github 的 URL、HTTP 方法和 HTTP 標頭 (header)。

profileResource 有新值時,profileerror 這兩個計算信號(computed signals)分別衍生出對應的個人資料和錯誤訊息。

// github-profile-card.component.html

@let status = profileResource.status();
@if (status === 'loading') {
    <p>Loading profile...</p>
} @else if (status === 'error') {
    <p>Error loading profile: {{ error() }}</p>
} @else {
    @if (profile(); as profile) {
        <div>
            <p>Username: {{ profile.login }}</p>
            <p>Name: {{ profile.name }}</p>
            <p>Bio: {{ profile.bio || 'N/A' }}</p>
        </div>
    }
}

HTML 模板會根據狀態 (state) 條件性顯示內容:

  • 當狀態是 loading 時,顯示「Loading profile...」;
  • 當狀態是 error 時,顯示錯誤訊息;
  • 最後,else 分支顯示個人資料的登入帳號(login)、名稱(name)和個人簡介(bio)。

建立 GithubProfileList 元件

接下來,我們建立一個 GithubProfileList 元件,該元件會將使用者名稱傳遞給 GithubProfileCard 以進行呈現。GithubProfileList 是可重複使用的,因為它也能接受任何使用者名稱的清單。

Vue 3 application

// GithubProfileList.vue

<script setup lang="ts">
    import GithubProfileCard from './GithubProfileCard.vue'

    const { usernames } = defineProps<{ usernames: string[] }>()
</script>

defineProps 中解構出 usernames 清單。

<template>
  <div class="header">
    <h1>Github Profile List (Vue 3 Ver.)</h1>
  </div>
  <GithubProfileCard v-for="username in usernames" :key="username" :username="username" />
</template>

v-for 指令 (directive) 迭代 username prop,並將 username 同時綁定為 keyusername 屬性。

// App.vue

<script setup lang="ts">
import GithubProfileList from './components/GithubProfileList.vue'

const usernames = ['johnsoncodehk', 'antfu', 'railsstudent', 'danielkellyio', 'hootlex', 'MooseSaeed']
</script>

<template>
  <GithubProfileList :usernames="usernames" />
</template>

App 元件會將 usernames 清單傳遞給 GithubProfileList

SvelteKit application

// github-profile-list.svelte

<script lang="ts"> 
    import GithubProfileCard from "./github-profile-card.svelte";
	import type { GithubProfileItem } from './github-profile-item.type';

    type Props = {
        profiles: GithubProfileItem[]
    };

    const { profiles }: Props = $props();
</script>

GithubProfileList$props() 解構(destructure)屬性(props)。

<div class="header">
    <h1>Github Profile List (Svelte ver.)</h1>
</div>
{#each profiles as profile (profile.key)}
    <GithubProfileCard {profile} />
{/each}

在模板中,#each 會迭代 profiles,並將 profile 綁定到 GithubProfileCard 的屬性(props)上。

// +page.svelte

<script lang="ts">
	import type { PageProps } from './$types';
	import GithubProfileList from '$lib/github-profile-list.svelte';

	const { data: results }: PageProps = $props();

	const { data: profiles } = results;
</script>
{#if !profiles || profiles.length === 0}
	<p>No profiles found.</p>
{:else}
	<GithubProfileList {profiles} />
{/if}

loader 函式會載入 profiles,並將其作為屬性傳遞給 GithubProfileList

Angular 20 application

import { ChangeDetectionStrategy, Component, input } from '@angular/core';
import { GithubProfileCardComponent } from './github-profile-card.coponent';

@Component({
    selector: 'app-github-profile-list',
    imports: [GithubProfileCardComponent],
    template: `
        <div class="header">
            <h1>Github Profile List (Angular Ver.)</h1>
        </div>
        @for (username of usernames(); track username) {
            <app-github-profile-card [username]="username"/>
        }
    `,
    changeDetection: ChangeDetectionStrategy.OnPush,
})
export class GithubProfileListComponent {
    usernames = input.required<string[]>();   
}

元件有一個必需的信號輸入 (signal input),為 usernames

@for 會迭代 usernames 輸入 (input) 並將其中的 username 傳遞給 GithubProfileCardComponentusername 信號輸入 (signal input)。

// app.component.ts

import { ChangeDetectionStrategy, Component } from '@angular/core';
import { GithubProfileListComponent } from './github/components/github-profile-list.component';

@Component({
  selector: 'app-root',
  imports: [GithubProfileListComponent],
  template: '<app-github-profile-list [usernames]="usernames" />',
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AppComponent {
  readonly usernames = ['johnsoncodehk', 'antfu', 'railsstudent', 'danielkellyio', 'hootlex', 'MooseSaeed'];   
}

AppComponent 定義了一個唯讀的 usernames 清單,並將其傳遞給 GithubProfileListComponent

我們已成功建立了 GithubProfileListGithubProfileCard 元件,並將名稱從父元件傳遞到子元件。子元件接收輸入並在模板中顯示它們。

Github Repositories

Github Pages

資源


上一篇
第18天 - Github Card 專案 第一部分 - 資料擷取
系列文
作為 Angular 專家探索 Vue 3 和 Svelte 520
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言