zod - 如何为“草稿”和“已发布”文章创建单独的模式,以及如何让 TypeScript 轻松区分它们(Astro v2 内容集合)
我正在尝试为我的博客在Astro中设置内容集合(Content Collections)。我几乎立刻就遇到了草稿文章的问题:它们缺少一些模式字段,Zod对此不满意。
深入研究 Zod 的文档后,我想到了 z.discriminatedUnion,因此“草稿”的架构将比已发布的帖子宽松得多:
// src/content/config.ts
// (simplified example)
const publishedPostSchema = z.object({
title: z.string(),
author: z.enum(["John Doe", "Jane Doe"]),
dateCreated: z.string().transform((str) => new Date(str)),
description: z.string(),
draft: z.literal(null),
});
const draftPostSchema = z.object({
title: z.string().nullable(),
author: z.enum(["John Doe", "Jane Doe"]).nullable(),
dateCreated: z
.string()
.transform((str) => new Date(str))
.nullable(),
draft: z.literal(true),
});
const exampleBlogCollection = defineCollection({
schema: z.discriminatedUnion("draft", [publishedPostSchema, draftPostSchema]),
});
export const collections = {
blog: exampleBlogCollection,
};
这可行,但我仍有两个小问题:
1. 这迫使我在所有已发布的文章上都必须有一个字面量的 null “draft” 字段。我更希望能够启用 null、false 和 undefined 值,但 z.discriminatedUnion 在判别字段上只接受字面量。有解决方法吗?
2. 生成的类型是一个判别联合类型,但仍然需要很多工作来确保 TypeScript 工具能确认我传递下去的内容不是可空的:
这不起作用(TypeScript 错误):
const allPosts = await getCollection('blog')
const publishedPosts = rawPosts.filter(post => post.data.draft === null)
publishedPosts .sort(
(a, b) =>
+(b.data.dateCreated) - +(a.data.dateCreated) // TS error: `dateCreated` can be `null`…
);
这确实有效,但是……有点啰嗦:
const allPosts = await getCollection('blog')
type PublishedPost = CollectionEntry<"blog"> & { data: { draft: null }}
function isPublishedPost(post: typeof allPosts[number]): post is PublishedPost {
return post.data.draft === null
}
const publishedPosts = allPosts.filter(isPublishedPost)
publishedPosts .sort(
(a, b) =>
+(b.data.dateCreated) - +(a.data.dateCreated) // O.K.
);
这是处理模式的“预期”方式吗,还是我遗漏了什么重要的东西?