查詢內容

本頁內容

EmDash 提供查詢函式來在 Astro 頁面和元件中檢索內容。這些函式遵循 Astro 的 即時內容集合 模式,返回帶有錯誤處理的結構化結果。

查詢函式

EmDash 匯出兩個主要查詢函式:

函式用途返回值
getEmDashCollection檢索內容類型的所有條目{ entries, error }
getEmDashEntry透過 ID 或 slug 檢索單個條目{ entry, error, isPreview }

emdash 匯入它們:

import { getEmDashCollection, getEmDashEntry } from "emdash";

取得所有條目

使用 getEmDashCollection 檢索內容類型的所有條目:

---
import { getEmDashCollection } from "emdash";

const { entries: posts, error } = await getEmDashCollection("posts");

if (error) {
  console.error("載入文章失敗:", error);
}
---

<ul>
  {posts.map((post) => (
    <li>{post.data.title}</li>
  ))}
</ul>

按語言環境篩選

i18n 已啟用時,按語言環境篩選以檢索特定語言的內容:

// 法語文章
const { entries: frenchPosts } = await getEmDashCollection("posts", {
	locale: "fr",
	status: "published",
});

// 使用目前請求的語言環境
const { entries: localizedPosts } = await getEmDashCollection("posts", {
	locale: Astro.currentLocale,
	status: "published",
});

對於單個條目,將 locale 作為第三個參數傳遞:

const { entry: post } = await getEmDashEntry("posts", "my-post", {
	locale: Astro.currentLocale,
});

當省略 locale 時,預設使用請求的目前語言環境。如果請求的語言環境不存在翻譯,則遵循後備鏈

按狀態篩選

僅檢索已發布或草稿內容:

// 僅已發布的文章
const { entries: published } = await getEmDashCollection("posts", {
	status: "published",
});

// 僅草稿
const { entries: drafts } = await getEmDashCollection("posts", {
	status: "draft",
});

限制結果

限制返回的條目數量:

// 取得最近的 5 篇文章
const { entries: recentPosts } = await getEmDashCollection("posts", {
	status: "published",
	limit: 5,
});

按分類篩選

按類別、標籤或自訂分類術語篩選條目:

// "news" 類別中的文章
const { entries: newsPosts } = await getEmDashCollection("posts", {
	status: "published",
	where: { category: "news" },
});

// 具有 "javascript" 標籤的文章
const { entries: jsPosts } = await getEmDashCollection("posts", {
	status: "published",
	where: { tag: "javascript" },
});

// 符合多個術語中的任何一個的文章
const { entries: featuredNews } = await getEmDashCollection("posts", {
	status: "published",
	where: { category: ["news", "featured"] },
});

當為單個分類提供多個值時,where 篩選器使用 OR 邏輯。

錯誤處理

當可靠性很重要時,始終檢查錯誤:

const { entries: posts, error } = await getEmDashCollection("posts");

if (error) {
	// 記錄並優雅處理
	console.error("載入文章失敗:", error);
	return new Response("伺服器錯誤", { status: 500 });
}

取得單個條目

使用 getEmDashEntry 透過其 ID 或 slug 檢索條目:

---
import { getEmDashEntry } from "emdash";
import { PortableText } from "emdash/ui";

const { slug } = Astro.params;
const { entry: post, error } = await getEmDashEntry("posts", slug);

if (error) {
  return new Response("伺服器錯誤", { status: 500 });
}

if (!post) {
  return Astro.redirect("/404");
}
---

<article>
  <h1>{post.data.title}</h1>
  <PortableText value={post.data.content} />
</article>

條目返回類型

getEmDashEntry 返回一個結果物件:

interface EntryResult<T> {
	entry: ContentEntry<T> | null; // 如果未找到則為 null
	error?: Error; // 僅在實際錯誤時設定(不是「未找到」)
	isPreview: boolean; // 如果正在檢視預覽/草稿內容則為 true
}

interface ContentEntry<T> {
	id: string;
	data: T;
	edit: EditProxy; // 視覺化編輯註釋
}

entry 中的 data 物件包含為內容類型定義的所有欄位。edit 代理提供視覺化編輯註釋(見下文)。

預覽模式

EmDash 透過中介軟體自動處理預覽。當 URL 包含有效的 _preview 權杖時,中介軟體會驗證它並設定請求上下文。然後,您的查詢函式會提供草稿內容,而無需任何特殊參數:

---
import { getEmDashEntry } from "emdash";

const { slug } = Astro.params;

// 不需要特殊的預覽處理 — 中介軟體會自動完成
const { entry, isPreview, error } = await getEmDashEntry("posts", slug);

if (error) {
  return new Response("伺服器錯誤", { status: 500 });
}

if (!entry) {
  return Astro.redirect("/404");
}
---

{isPreview && (
  <div class="preview-banner">
    正在檢視預覽。此內容尚未發布。
  </div>
)}

<article>
  <h1>{entry.data.title}</h1>
  <PortableText value={entry.data.content} />
</article>

視覺化編輯

查詢函式返回的每個條目都包含一個 edit 代理,用於註釋您的範本。將其展開到元素上以為經過身分驗證的編輯器啟用內聯編輯:

<article {...entry.edit}>
  <h1 {...entry.edit.title}>{entry.data.title}</h1>
  <div {...entry.edit.content}>
    <PortableText value={entry.data.content} />
  </div>
</article>

在編輯模式下,{...entry.edit.title} 會產生一個 data-emdash-ref 屬性,視覺化編輯工具列使用該屬性來啟用內聯編輯。在生產環境中,代理展開不會產生輸出。

排序結果

getEmDashCollection 不保證排序順序。在範本中對結果進行排序:

const { entries: posts } = await getEmDashCollection("posts", {
	status: "published",
});

// 按發布日期排序,最新的在前
const sorted = posts.sort(
	(a, b) => (b.data.publishedAt?.getTime() ?? 0) - (a.data.publishedAt?.getTime() ?? 0),
);

常見排序模式

// 按標題字母順序
posts.sort((a, b) => a.data.title.localeCompare(b.data.title));

// 按自訂順序欄位
posts.sort((a, b) => (a.data.order ?? 0) - (b.data.order ?? 0));

// 隨機順序
posts.sort(() => Math.random() - 0.5);

TypeScript 類型

為您的集合產生 TypeScript 類型:

npx emdash types

這將建立 .emdash/types.ts,其中包含每個集合的介面。使用它們來實現類型安全:

import { getEmDashCollection, getEmDashEntry } from "emdash";
import type { Post } from "../.emdash/types";

// 類型安全的集合查詢
const { entries: posts } = await getEmDashCollection<Post>("posts");
// posts 是 ContentEntry<Post>[]

// 類型安全的條目查詢
const { entry: post } = await getEmDashEntry<Post>("posts", "my-post");
// post 是 ContentEntry<Post> | null

靜態 vs. 伺服器渲染

EmDash 內容適用於靜態和伺服器渲染的頁面。

靜態(預渲染)

對於靜態頁面,使用 getStaticPaths 在建置時產生路由:

---
import { getEmDashCollection, getEmDashEntry } from "emdash";

export async function getStaticPaths() {
  const { entries: posts } = await getEmDashCollection("posts", {
    status: "published",
  });

  return posts.map((post) => ({
    params: { slug: post.data.slug },
  }));
}

const { slug } = Astro.params;
const { entry: post } = await getEmDashEntry("posts", slug);
---

伺服器渲染

對於伺服器渲染的頁面,直接查詢內容:

---
export const prerender = false;

import { getEmDashEntry } from "emdash";

const { slug } = Astro.params;
const { entry: post, error } = await getEmDashEntry("posts", slug);

if (error) {
  return new Response("伺服器錯誤", { status: 500 });
}

if (!post) {
  return new Response(null, { status: 404 });
}
---

效能考量

快取

EmDash 使用 Astro 的即時內容集合,它會自動處理快取。對於伺服器渲染的頁面,請考慮新增 HTTP 快取標頭:

---
const { entries: posts } = await getEmDashCollection("posts", {
  status: "published",
});

// 快取 5 分鐘
Astro.response.headers.set("Cache-Control", "public, max-age=300");
---

避免冗餘查詢

查詢一次並將資料傳遞給元件:

---
import { getEmDashCollection } from "emdash";
import PostList from "../components/PostList.astro";
import Sidebar from "../components/Sidebar.astro";

// 查詢一次
const { entries: posts } = await getEmDashCollection("posts", {
  status: "published",
});

const featured = posts.filter((p) => p.data.featured);
const recent = posts.slice(0, 5);
---

<PostList posts={featured} />
<Sidebar posts={recent} />

下一步