第 28 天 - 取得貼文作者
在第 28 天,我使用貼文的 userId
呼叫 users
端點來取得使用者名稱。
在 Vue 3 中,我建立了另一個 composable,回傳一個帶有 name
屬性的 user
物件。在 Angular 20 中,是一個使用實驗性 httpResource
的 User
服務,當貼文更新時會取得使用者。在 SvelteKit 中,我在 load
函式中新增邏輯,透過貼文的 userId
查詢使用者。
Framework | Approach |
---|---|
Vue 3 | useUser Composable |
SvelteKit | Make a new request to the users endpoint to retrieve a user by user id |
Angular 20 | A user httpResource to retrieve a user by user id in a service |
建立一個 User
類型以取得 ID 和名稱。
export type User = {
id: number;
name: string;
}
在 src/composables
資料夾下建立一個 useUser
composable。
實作一個 fetchOne
函式並宣告一個 user
的 ref,以透過 ID 取得使用者。
import type { User } from '@/types/user'
import { ref } from 'vue'
export function useUser() {
const user = ref<User | null>(null)
const baseUrl = `https://jsonplaceholder.typicode.com/users/${id}`
function fetchOne(id: number, signal: AbortSignal) {
return fetch(baseUrl, { signal })
.then((response) => response.json() as Promise<User>)
.then((data) => (user.value = data))
}
return {
user,
fetchOne,
}
}
fetchOne
函式會向 baseUrl
發出請求取得使用者,並將該使用者指派給 user.value
。
接著,該 composable 回傳 user
和 fetchOne
,以便 Post 元件可以存取。
我修改了程式碼,使用 async/await 來取得貼文和貼文作者。
export type PostWitUser = {
post: Post | undefined;
user: User | undefined;
};
import { BASE_URL } from '$lib/constants/posts.const';
import type { Post } from '$lib/types/post';
import type { PostWitUser, User } from '$lib/types/user';
import type { PageLoad } from './$types';
// retreive a post by an ID
export const load: PageLoad = async ({ params, fetch }): Promise<PostWitUser> => {
console.log('params', params);
try {
const response = await fetch(`${BASE_URL}/posts/${params.id}`);
const post = (await response.json()) as Post;
const userResponse = post ? await fetch(`${BASE_URL}/users/${post?.userId}`) : undefined;
const user = userResponse ? ((await userResponse.json()) as User) : undefined;
return {
post,
user
};
} catch (error) {
console.error('Error fetching a post:', error);
return {
post: undefined,
user: undefined
};
}
};
當第一個請求成功取得貼文後,第二個請求會使用 userId
來取得使用者。
安裝 zod 以解析 httpResource
的結果。
npm install --save-exact zod
import z from 'zod';
export const userSchema = z.object({
id: z.number(),
name: z.string(),
});
export type User = z.infer<typeof userSchema>;
建立一個 userSchema
並推斷 User
類型。
import { httpResource } from '@angular/common/http';
import { Injectable, Signal } from '@angular/core';
import { Post } from '../types/post.type';
import { User, userSchema } from '../types/user.type';
const BASE_URL = 'https://jsonplaceholder.typicode.com/users';
@Injectable({
providedIn: 'root',
})
export class UserService {
createUserResource(post: Signal<Post | undefined>) {
return httpResource<User>(
() => {
const result = post();
return result ? `${BASE_URL}/${result.userId}` : undefined;
},
{
defaultValue: undefined,
equal: (a, b) => a?.id === b?.id,
parse: userSchema.parse,
},
).asReadonly();
}
}
這是一種在服務中建立 httpResource
的技術。createUserResource
方法接受一個 post
信號以追蹤其變化。當貼文更新時,httpResource
會發出 HTTP 請求以根據使用者 ID 取得使用者。
{
defaultValue: undefined,
equal: (a, b) => a?.id === b?.id,
parse: userSchema.parse,
}
預設值為 undefined。當兩個使用者具有相同 ID 且不發出 HTTP 請求時,視為相等。userSchema.parse
將原始資料解析為 User
物件。
在 Post
檢視中,匯入 usePost
組合函式並解構 fetch
和 user
。
在 Post
元件中,匯入 usePost
並調用 fetchOne
以藉由 ID 取得貼文。
import { useRoute } from 'vue-router'
import { usePost } from '@/composables/usePost'
import { useUser } from '@/composables/useUser'
const { post, fetchOne } = usePost()
const { user, fetchOne: fetchUser } = useUser()
const { params } = useRoute();
fetchOne(+params.id)
fetchUser(post?.userId)
<template>
<div v-if="isLoading" class="text-center my-10">
Loading...
</div>
<div v-if="post && user" class="mb-10">
<h1 class="text-3xl">{{ post.title }}</h1>
<div class="text-gray-500 mb-10">by {{ user.name }}</div>
<div class="mb-10">{{ post.body }}</div>
</div>
</template>
在模板中,將硬編碼的名稱替換為 {{ user.name }}
。
<script lang="ts">
import type { PageProps } from './$types';
const { data }: PageProps = $props();
const { post, user } = data;
</script>
在 posts/[id]/+page.svelte
中,load
函式透過 ID 取得貼文,並根據貼文的 userId
取得使用者。
<div class="mb-10">
<h1 class="text-3xl">{ post?.title }</h1>
<div class="text-gray-500 mb-10">by { user?.name }</div>
<div class="mb-10">{ post?.body }</div>
</div>
現在,模板可以透過插值 { user?.name }
顯示使用者名稱。
import { ChangeDetectionStrategy, Component, computed, inject, input } from '@angular/core';
import { UserService } from './services/user.service';
import { Post } from './types/post.type';
@Component({
... no change ...
})
export default class PostComponent {
readonly userService = inject(UserService);
post = input<Post>();
userRef = this.userService.createUserResource(this.post);
user = computed(() => (this.userRef.hasValue() ? this.userRef.value() : undefined));
}
PostComponent
注入 userService
並呼叫 createUserResource
方法來建立 userRef
。
user
是一個計算信號 (computed signal),用來計算 user
的值。當 userRef
有值時,this.userRef.value()
會回傳有效的 User
物件。否則,user
會取得 undefined。
內嵌模板無需更改。
此時,Post
檢視中有一個錯誤,導致 user
未定義。當在貼文資料到達之前調用 fetchUser(post?.userId)
時會發生此情況。解決方法是使用 watch
監聽 post
ref 的變化。當 ref 收到新值時,監聽器回呼函式會呼叫 fetchUser
函式以取得使用者名稱。
watch(() => ({ ...post.value }), (newPost, oldPost, onCleanup) => {
const controller = new AbortController()
if (newPost?.userId) {
fetchUser(newPost.userId, controller.signal)
}
onCleanup(() => {
controller.abort()
})
})
將一個 getter 函式傳遞給 watch
以追蹤貼文的更新。當新的貼文有使用者 ID 時,即會用該 ID 取得一個新的使用者並顯示。
我們已成功顯示貼文的使用者。