題目
任務目標:建立一個動態的「隨機推薦」組件。
- 建立一個名為
RandomQuote.astro的組件。 - 在該組件的 Code Fence 中,使用
fetch()從 API 抓取一句隨機名言(例如:https://api.quotable.io/random)。 - 在
about.astro頁面中,以server:defer的方式引入這個組件。 - 設定一個具備 Loading 樣式的
fallback內容。 - 進階挑戰: 嘗試將原本的部落格清單頁面
blog.astro改為混合模式——頁面頂部標題是靜態的,但文章清單改由server:defer渲染。
目前專案結構
📁 .astro/
📁 .vscode/
📁 node_modules/
📁 public/
└─ favicon.svg
📁 src/
├─ 📁 components/
│ ├─ DescriptionControl.jsx
│ ├─ LikeButton.jsx
│ └─ ServerTime.astro
├─ 📁 content/
│ ├─ 📁 blog/
│ │ └─ post-1.md
│ └─ config.ts
├─ 📁 layouts/
│ └─ BaseLayout.astro
└─ 📁 pages/
├─ 📁 posts/
│ └─ [id].astro
├─ about.astro
├─ blog.astro
└─ index.astro
.gitignore
astro.config.mjs
package-lock.json
package.json
README.md
tscon
1_新增 src/components/RandomQuote.astro
---
let random
try {
const res = await fetch('https://zenquotes.io/api/random')
const data = await res.json()
console.log(data)
random = data
} catch (err) {
console.error(err)
}
// 模擬一個慢速的資料庫查詢
await new Promise((resolve) => setTimeout(resolve, 2000))
---
<div class="server-box">
<p>伺服器隨機生成:{random[0].q}</p>
<p>(此組件是延遲載入的 Server Island)</p>
</div>
<style>
.server-box {
border: 2px dashed #ff5d01;
padding: 1rem;
background: #fff5f0;
}
</style>
2_修改 src/pages/about.astro
- 匯入
RandomQuote物件 - html 區塊找一個地方放
RandomQuote <p>沒有加slot="fallback"是不會顯示的喔
---
import BaseLayout from '../layouts/BaseLayout.astro'
import DescriptionControl from '../components/DescriptionControl'
import RandomQuote from '../components/RandomQuote.astro'
const pageTitle = '關於我'
const identity = {
firstName: '小明',
hobbies: ['reading', 'gaming', 'coding'],
}
const description =
'這段文字旨在模擬實際使用中的描述欄位,長度超過二十個字以符合需求。'
---
<BaseLayout pageTitle={pageTitle} pageName="about">
<h2>{identity.firstName}</h2>
<h3>我的愛好</h3>
<ul>
{identity.hobbies.map((x) => <li>{x}</li>)}
</ul>
<h3>描述</h3>
<DescriptionControl description={description} client:visible />
<h3>隨機生成</h3>
<RandomQuote server:defer>
<p slot="fallback" class="loading">等待隨機生成....</p>
</RandomQuote>
</BaseLayout>
3_進階挑戰
3_1 新增 src/components/PostsQuote.astro
---
import { getCollection } from 'astro:content'
import type { CollectionEntry } from 'astro:content'
const posts: CollectionEntry<'blog'>[] = await getCollection('blog')
// 模擬資料庫查詢
await new Promise((resolve) => setTimeout(resolve, 2000))
---
<ul>
{
posts
.sort((a, b) => {
const a_date = a.data.pubDate.toDateString()
const b_date = b.data.pubDate.toDateString()
return b_date.localeCompare(a_date)
})
.map((post) => (
<li>
<a href={`/posts/${post.id}`}>{post.data.title}</a>
<span> - {post.data.pubDate.toLocaleDateString()} </span>
</li>
))
}
</ul>
3_2 修改 src/pages/blog.astro
---
// import { getCollection } from 'astro:content'
// import type { CollectionEntry } from 'astro:content'
import BaseLayout from '../layouts/BaseLayout.astro'
import PostsQuote from '../components/PostsQuote.astro'
const pageTitle = '我的部落格'
// const allPosts: CollectionEntry<'blog'>[] = await getCollection('blog')
---
<BaseLayout pageTitle={pageTitle}>
<PostsQuote server:defer>
<p slot="fallback" class="loading">文章列表載入中...</p>
</PostsQuote>
<!-- <ul>
{
allPosts
.sort((a, b) => {
const b_date = b.data.pubDate.toDateString()
const a_date = a.data.pubDate.toDateString()
return b_date.localeCompare(a_date)
})
.map((post) => (
<li>
<a href={`/posts/${post.id}`}>{post.data.title}</a>
<span> - {post.data.pubDate.toLocaleDateString()} </span>
</li>
))
}
</ul> -->
</BaseLayout>