查询内容

本页内容

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} />

下一步