Astro v2 Content Collections:草稿与发布文章Schema区分优化
Astro Content Collections 草稿文章Schema处理优化方案
我在配置Astro的Content Collections时,遇到草稿文章因缺少Schema字段导致Zod校验失败的问题,于是用z.discriminatedUnion分别定义了发布文章的严格Schema和草稿的宽松Schema,代码如下:
// 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, };
该方案可运行,但存在两个问题:
- 必须为所有发布文章设置
draft: null,希望支持null、false、undefined值,但z.discriminatedUnion仅接受字面量作为区分字段 - 生成的联合类型需要手动编写类型守卫才能完成TypeScript类型收窄,代码过于繁琐
问题1:支持多值发布状态标识
不用discriminatedUnion,改用z.preprocess统一处理draft字段,将false/undefined转换为null,再结合z.union实现兼容:
// src/content/config.ts import { defineCollection, z } from 'astro:content'; // 预处理draft字段:将false/undefined转为null,保留true const draftField = z.preprocess( (val) => val === false || val === undefined ? null : val, z.union([z.literal(true), z.literal(null)]) ); // 定义基础Schema,抽离公共字段避免重复 const basePostSchema = z.object({ title: z.string(), author: z.enum(["John Doe", "Jane Doe"]), dateCreated: z.string().transform(str => new Date(str)), }); // 发布文章Schema:基于基础Schema扩展,强制draft为null(含转换后的false/undefined) const publishedPostSchema = basePostSchema.extend({ description: z.string(), draft: draftField.refine(val => val === null, "发布文章的draft需为null/false/undefined"), }); // 草稿文章Schema:基础字段设为可选,强制draft为true const draftPostSchema = basePostSchema.partial().extend({ draft: draftField.refine(val => val === true, "草稿文章的draft需设为true"), }); const blogCollection = defineCollection({ schema: z.union([publishedPostSchema, draftPostSchema]), }); export const collections = { blog: blogCollection, };
这样发布文章的draft字段支持null、false或不写(undefined),都会被统一处理为null,满足校验要求。
问题2:简化TypeScript类型收窄
Astro的getCollection支持直接传入过滤函数作为第二个参数,自动完成类型收窄,无需手动编写类型守卫:
async function getPublishedPosts() { return await getCollection('blog', ({ data }) => data.draft === null); } // 调用后自动收窄为发布文章类型,无TS错误 const publishedPosts = await getPublishedPosts(); publishedPosts.sort( (a, b) => +b.data.dateCreated - +a.data.dateCreated );
如果需要从已获取的全量文章中过滤,可利用Zod自动生成的类型简化定义:
import type { z } from 'astro:content'; import type { collections } from './src/content/config'; // 从Schema中提取博客文章类型 type BlogPost = z.infer<typeof collections.blog.schema>; // 提取发布文章类型(自动收窄) type PublishedPost = Extract<BlogPost, { draft: null }>; // 过滤时用类型断言简化 const publishedPosts = allPosts.filter(post => post.data.draft === null) as PublishedPost[];
总结
这是处理Astro Content Collections草稿/发布状态的常规优化思路,核心是通过preprocess统一字段格式,利用Astro内置的过滤能力简化类型收窄,避免冗余代码。
内容的提问来源于stack exchange,提问作者HynekS
相关产品推荐
相关产品推荐

