其他
Node.js + TypeScript 项目需要落地…
适用于在 Node.js + TypeScript 全栈项目中按 Payload CMS、Vue、Pinia、Zod 和通用最佳实践搭建结构、定义数据与校验、管理状态并处理错误与测试。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 35/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 39/100 ×0.15
适用场景
Node.js + TypeScript特定
解决思路
- 按项目结构组织 Payload CMS + Next.js 项目:根目录放置 payload.config.ts 和 next.config.js,src/ 下划分 app/(Next.js app directory)、collections/(Payload collections)、blocks/(自定义 blocks)、fields/(自定义 fields)、access/(访问控制函数)。
- 定义 Payload 集合时从 payload/types 导入 CollectionConfig,创建 Posts: CollectionConfig 配置对象并 export default。
- 为 Payload 集合设置 slug: 'posts'。
- 在 Payload 集合的 admin 中设置 useAsTitle: 'title'。
- 在 Payload 集合的 access 中设置 read: () => true;create、update、delete 均使用 ({ req: { user } }) => Boolean(user),要求已登录用户。
- 在 Payload 集合 fields 中配置 title 为 text 且 required: true;content 为 richText;publishedDate 为 date。
- 在 app/api/posts/route.ts 中实现 GET,从 @/lib/payload 导入 getPayloadClient。
- 在 GET 中 await getPayloadClient() 获取 payload 实例。
- 调用 payload.find({ collection: 'posts', limit: 10 }) 查询 posts。
- 使用 Response.json(posts) 返回查询结果。
- Vue 组件使用 <script setup lang="ts"> 编写。
- 在 Vue 中用 interface User 定义 { id: number; name: string; email: string }。
- 用 defineProps<{ userId: number }>() 声明 props。
- 用 defineEmits<{ (e: 'update', user: User): void }>() 声明 emits。
- 用 ref<User | null>(null) 保存 user,用 ref(false) 保存 isLoading。
- 用 computed(() => user.value?.name ?? 'Unknown') 生成 displayName。
- 实现 async fetchUser:设置 isLoading.value = true,try 中 fetch(`/api/users/${props.userId}`) 并赋值 user.value = await response.json(),finally 中 isLoading.value = false。
- 在 onMounted(fetchUser) 中触发初始加载。
- 模板中用 v-if="isLoading" 显示 Loading...,v-else-if="user" 显示 h2 的 displayName 和 p 的 user.email。
- 将请求逻辑封装为 composables/useApi.ts。
- 在 useApi<T>(url: string) 中创建 data = ref<T | null>(null)、error = ref<Error | null>(null)、isLoading = ref(false)。
- 在 execute 中先设置 isLoading.value = true、error.value = null。
- 在 execute 的 try 中 fetch(url),若 !response.ok 则 throw new Error('Request failed'),否则 data.value = await response.json()。
- 在 execute 的 catch 中把异常写入 error.value = e as Error。
- 在 execute 的 finally 中设置 isLoading.value = false。
- 从 useApi 返回 { data, error, isLoading, execute }。
- 用 Pinia 定义 stores/user.ts,从 pinia 导入 defineStore。
- 定义 UserState:user: User | null、isAuthenticated: boolean。
- 用 defineStore('user', { state: (): UserState => ({ user: null, isAuthenticated: false }), ... }) 创建 useUserStore。
- 在 getters 中提供 displayName: (state) => state.user?.name ?? 'Guest'。
- 在 actions 中实现 async login(credentials: LoginCredentials):await authService.login(credentials),然后 this.user = user、this.isAuthenticated = true。
- 在 actions 中实现 logout():this.user = null、this.isAuthenticated = false。
- 用 Zod 定义 UserSchema:z.object({ id: z.number(), name: z.string().min(1), email: z.string().email(), role: z.enum(['user', 'admin']), createdAt: z.date() })。
- 用 z.infer<typeof UserSchema> 推导 User 类型。
- 用 UserSchema.omit({ id: true, createdAt: true }) 创建 CreateUserSchema,并用 z.infer<typeof CreateUserSchema> 推导 CreateUserInput。
- 在 createUser(input: unknown): User 中用 CreateUserSchema.parse(input) 校验输入。
- 在 createUser 返回时展开校验后的输入,并补充 id: generateId()、createdAt: new Date()。
- 需要安全解析时用 UserSchema.safeParse(input);失败时 console.error(result.error.issues) 并返回 null,成功时返回 result.data。
- 编写 Express 中间件 validate<T extends z.ZodSchema>(schema: T):返回 (req, res, next) 函数。
- 在 validate 中间件中对 req.body 执行 schema.safeParse(req.body)。
- 若 result.success 为 false,返回 res.status(400).json({ errors: result.error.issues })。
- 若成功,把 req.body 替换为 result.data,然后调用 next()。
- 按 app.post('/users', validate(CreateUserSchema), createUserHandler) 方式使用校验中间件。
- 错误处理:使用自定义错误类。
- 错误处理:实现全局错误处理器。
- 错误处理:带上下文记录错误。
- 错误处理:返回合适的 HTTP 状态码。
- 异步模式:一致使用 async/await。
- 异步模式:处理 Promise 拒绝。
- 异步模式:实现合适的超时。
- 异步模式:并行操作使用 Promise.all。
- 测试:为工具函数写单元测试。
- 测试:为 API 写集成测试。
- 测试:使用测试夹具。
- 测试:模拟外部依赖。
适用边界 · 注意事项
- 非 Node.js/TypeScript 项目,或未使用 Next.js、Payload CMS、Vue、Pinia、Zod、Express 的技术栈时,不应直接套用其中框架专属配置。
- 若项目已有不同的目录结构、状态管理、校验方案或错误处理规范,应遵循项目规范而不是生搬硬套。
- 简单脚本或一次性原型不需要完整集合定义、store、中间件和测试分层时可裁剪使用。