综合练习
为 platform 组件写严格 utility 类型。
40 分钟
综合练习
本课整合 typescript-advanced-essentials 全部主题,为 platform 组件与 API 层编写严格 utility 类型。练习贯穿 typescript-essentials 基础、nestjs-essentials 后端 DTO 模式与 nextjs-essentials 前端数据流,完成 TypeScript 路线图闭环。
学习目标
- 设计可复用的类型工具库
- 为 CRUD API 建模 Create/Update/List 类型
- 在 React 组件中应用严格 props 与 async 数据类型
任务一:API 类型工具
interface Lesson {
id: number;
slug: string;
title: string;
contentMd: string;
trackId: number;
publishedAt: Date | null;
createdAt: Date;
updatedAt: Date;
}
// 实现以下类型
type CreateLessonInput = Omit<Lesson, "id" | "createdAt" | "updatedAt" | "publishedAt">;
type UpdateLessonInput = Partial<Pick<Lesson, "title" | "contentMd" | "publishedAt">>;
type LessonListItem = Pick<Lesson, "slug" | "title" | "publishedAt">;
type ApiResponse<T> =
| { ok: true; data: T }
| { ok: false; error: { code: string; message: string } };
type LessonResponse = ApiResponse<Lesson>;
type LessonListResponse = ApiResponse<LessonListItem[]>;
任务二:条件类型提取
type UnwrapApiResponse<T> = T extends ApiResponse<infer D> ? D : never;
type LessonData = UnwrapApiResponse<LessonResponse>; // Lesson
type LessonListData = UnwrapApiResponse<LessonListResponse>; // LessonListItem[]
function assertOk<T>(res: ApiResponse<T>): asserts res is { ok: true; data: T } {
if (!res.ok) {
throw new Error(res.error.message);
}
}
任务三:React 组件 props
type LessonPageProps = {
lesson: LessonListItem;
contentMd: string;
onComplete?: () => void;
};
// 从 props 派生
type LessonHeaderProps = Pick<LessonPageProps, "lesson">;
// 严格 async loader 返回类型
type LoaderResult = {
lesson: Lesson;
related: LessonListItem[];
};
async function loadLesson(slug: string): Promise<LoaderResult> {
const res = await fetch(`/api/lessons/${slug}`);
const json = (await res.json()) as ApiResponse<LoaderResult>;
assertOk(json);
return json.data;
}
任务四:类 + 装饰器(可选)
// 模拟 Nest 风格 validation pipe
function ValidateTarget(target: Function) {
Reflect.defineMetadata("validated", true, target);
}
@ValidateTarget
class CreateLessonDto implements CreateLessonInput {
constructor(
public slug: string,
public title: string,
public contentMd: string,
public trackId: number,
) {}
}