Astro - 基礎入門4.第四課練習題實作

更新 發佈閱讀 10 分鐘

題目

任務目標:建立一個動態的「隨機推薦」組件。

  1. 建立一個名為 RandomQuote.astro 的組件。
  2. 在該組件的 Code Fence 中,使用 fetch() 從 API 抓取一句隨機名言(例如:https://api.quotable.io/random)。
  3. 在 about.astro 頁面中,以 server:defer 的方式引入這個組件。
  4. 設定一個具備 Loading 樣式的 fallback 內容。
  5. 進階挑戰: 嘗試將原本的部落格清單頁面 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

  1. 匯入 RandomQuote 物件
  2. html 區塊找一個地方放 RandomQuote
  3. <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>
留言
avatar-img
李昀瑾的沙龍
0會員
25內容數
李昀瑾的沙龍的其他內容
2026/01/11
第四課:Server Islands 與 SSR (伺服器端渲染) 在之前的課程中,我們的網站是「靜態」的。 但在真實應用中,有些內容不能是靜態的(例如:用戶登入狀態、即時庫存、或是根據地理位置推薦的內容)。 1. 核心觀念:Server Islands vs. Client Islands
2026/01/11
第四課:Server Islands 與 SSR (伺服器端渲染) 在之前的課程中,我們的網站是「靜態」的。 但在真實應用中,有些內容不能是靜態的(例如:用戶登入狀態、即時庫存、或是根據地理位置推薦的內容)。 1. 核心觀念:Server Islands vs. Client Islands
2026/01/10
第三課:Astro Islands —— 混合開發與互動性 核心觀念:預設 0 JS Astro 就像一個「大洋」,預設所有組件都是靜態的 HTML(沒有 JS)。如果你需要互動(如:彈窗、購物車、即時搜尋),你就在大洋中丟入一個「孤島 (Island)」。
2026/01/10
第三課:Astro Islands —— 混合開發與互動性 核心觀念:預設 0 JS Astro 就像一個「大洋」,預設所有組件都是靜態的 HTML(沒有 JS)。如果你需要互動(如:彈窗、購物車、即時搜尋),你就在大洋中丟入一個「孤島 (Island)」。
2026/01/10
Astro - 基礎入門3.第三課練習題實作
2026/01/10
Astro - 基礎入門3.第三課練習題實作
看更多