TypeScript映射类型结合条件类型未生效,无法校验非法字段
问题分析
问题出在board的类型标注上:你直接把board标注为BoardType,而BoardType中的columns被定义为Array<ColumnType>,这会让TypeScript丢失数组中每个列的具体id字面量类型,只保留string类型。因此PulseType<typeof board.columns>中的Columns[number]["id"]会被解析为string,导致data对象的键被允许是任意string,自然不会对非法键"ehh"给出警告。
解决方案
有两种可行的修复方式:
方式一:移除board的显式类型标注,保留完整字面量类型
去掉board的: BoardType标注,同时用as const强制TypeScript保留数组中每个列的具体字面量类型,让PulseType能正确提取合法的列id:
type ElementType = "ShortText" | "Status" | "Number"; type Column = { id: string; name: string; element: ElementType; }; type StatusType = Column & { options: { option: string; color: string }[]; }; type ColumnType = Column | StatusType; type ColumnValue<T extends ElementType> = T extends "ShortText" ? string : T extends "Status" ? string : T extends "Number" ? number : never; type PulseType<Columns extends ColumnType[]> = { id: string; board: string; data: { [K in Columns[number]["id"]]?: ColumnValue<Extract<Columns[number], { id: K }>["element"]>; }; }; // 移除显式类型标注,用as const锁定字面量类型 const board = { id: "board-1", name: "Sample Board", columns: [ { id: "Name", name: "Full Name", element: "ShortText" }, { id: "Status", name: "To DOs", element: "Status", options: [ { option: "To Do", color: "grey" }, { option: "In Progress", color: "blue" }, { option: "Done", color: "green" }, ], }, { id: "Age", name: "Age", element: "Number" }, ], } as const; const pulses: PulseType<typeof board.columns> = { id: "1", board: "board-1", data: { Name: "John Doe", Status: "Done", Age: 25, ehh: "error", // 现在会正确报错:类型"{ Name: string; Status: string; Age: number; ehh: string; }"不能赋值给类型"Partial<Record<"Name" | "Status" | "Age", string | number>>" }, };
方式二:将BoardType改为泛型,显式保留列的具体类型
如果需要明确标注board的类型,可以把BoardType改成泛型,让它接收列数组的具体类型作为参数:
type ElementType = "ShortText" | "Status" | "Number"; type Column = { id: string; name: string; element: ElementType; }; type StatusType = Column & { options: { option: string; color: string }[]; }; type ColumnType = Column | StatusType; // 泛型BoardType,保留columns的具体类型信息 type BoardType<Columns extends ColumnType[]> = { id: string; name: string; columns: Columns; }; type ColumnValue<T extends ElementType> = T extends "ShortText" ? string : T extends "Status" ? string : T extends "Number" ? number : never; type PulseType<Columns extends ColumnType[]> = { id: string; board: string; data: { [K in Columns[number]["id"]]?: ColumnValue<Extract<Columns[number], { id: K }>["element"]>; }; }; // 用泛型标注board,传入具体的列类型数组 const board: BoardType<[ { id: "Name"; name: string; element: "ShortText" }, StatusType & { id: "Status"; element: "Status" }, { id: "Age"; name: string; element: "Number" } ]> = { id: "board-1", name: "Sample Board", columns: [ { id: "Name", name: "Full Name", element: "ShortText" }, { id: "Status", name: "To DOs", element: "Status", options: [ { option: "To Do", color: "grey" }, { option: "In Progress", color: "blue" }, { option: "Done", color: "green" }, ], }, { id: "Age", name: "Age", element: "Number" }, ], }; const pulses: PulseType<typeof board.columns> = { id: "1", board: "board-1", data: { Name: "John Doe", Status: "Done", Age: 25, ehh: "error", // 同样会触发类型错误 }, };
关键说明
- 核心问题是类型拓宽:当你用
BoardType标注board时,数组中每个列的id从字面量类型(如"Name")被拓宽为string,导致PulseType无法识别具体的合法键。 - 使用
as const可以强制TypeScript保留字面量类型,避免拓宽;泛型BoardType则通过显式传递类型参数,保留列的具体结构信息。
内容的提问来源于stack exchange,提问作者askrill
相关产品推荐
相关产品推荐

