示範中使用卡片佈局 (card layout) 顯示 Github 使用者。GithubProfileList
會迭代一個使用者名稱的清單,並在 GithubProfileCard
中顯示每位使用者。
// 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)username
、profile
和 error
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
不為空白時,會顯示錯誤訊息。
// github-profile.type.ts
export type GithubProfileItem = {
key: number;
profile?: GithubProfile;
error?: string
}
GithubProfileItem
類型由 profile
或 error
屬性組成。
// 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 中解構出 profile
和 error
。
{#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
控制流程語法會檢查 profile
和 error
兩者的值。當 profile
被定義時,if 分支會顯示 Github 個人資料;而當 error
不為空白時,else-if 分支會顯示錯誤訊息。
// 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
有新值時,profile
和 error
這兩個計算信號(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) 條件性顯示內容:
接下來,我們建立一個 GithubProfileList
元件,該元件會將使用者名稱傳遞給 GithubProfileCard
以進行呈現。GithubProfileList
是可重複使用的,因為它也能接受任何使用者名稱的清單。
// 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
同時綁定為 key
和 username
屬性。
// 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
。
// 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
。
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
傳遞給 GithubProfileCardComponent
的 username
信號輸入 (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
。
我們已成功建立了 GithubProfileList
和 GithubProfileCard
元件,並將名稱從父元件傳遞到子元件。子元件接收輸入並在模板中顯示它們。