使用泛型在TypeScript类中处理动态键的类型问题
问题描述
我在TypeScript中使用extends keyof时遇到类型系统问题,想要创建一个灵活的N叉树类,允许动态指定TreeContent对象中存储子节点、数据的键,以及Data对象中标签的键。
调用方式如下:
const tree = new Tree< TestTree, "children", "data", TestTreeData, "label" >(treeTestData, "children", "data", "label");
其中children用于访问TestTree中的子节点数据,data和label同理,label用于在TestTreeData中查找标签。目标是构建一个能从使用不同别名存储子节点和数据的树形数据中构造N叉树的灵活类。
类定义如下:
export class Tree< TreeContent, ChildKey extends keyof TreeContent, DataKey extends keyof TreeContent, Data, LabelKey extends keyof Data > { id: string; children: NodeModel<Data>[]; childrenKey: ChildKey; dataKey: DataKey; labelKey: LabelKey; data: Data; constructor( treeContent: TreeContent, _childrenKey: ChildKey, _dataKey: DataKey, _labelKey: LabelKey, _id = uuidv4() ) { this.dataKey = _dataKey; this.childrenKey = _childrenKey; this.labelKey = _labelKey; this.children = []; this.id = _id; if ( !(_childrenKey in treeContent) || !Array.isArray(treeContent[this.childrenKey]) ) { throw Error(`Could not find data in ${String(this.childrenKey)}`); } const children = treeContent[this.childrenKey]; // 此处TypeScript无法识别children为数组类型,无法调用push等数组方法 }
问题在于,即使显式检查了treeContent[this.childrenKey]是数组,TypeScript仍将const children: TreeContent[ChildKey]识别为非数组类型。希望避免使用类型断言,让TypeScript自动识别该值为TreeContent[]类型,同时让TreeContent[DataKey]对应泛型Data对象。
解决方案
要让TypeScript正确推断类型,需要在泛型约束中明确指定TreeContent[ChildKey]是TreeContent[]类型,TreeContent[DataKey]是Data类型。以下是两种可行的修改方式:
方式一:使用条件类型约束
export class Tree< TreeContent, ChildKey extends keyof TreeContent, DataKey extends keyof TreeContent, Data = TreeContent[DataKey], LabelKey extends keyof Data = keyof Data > // 通过条件类型确保ChildKey对应值为TreeContent数组,DataKey对应值为Data extends ( TreeContent[ChildKey] extends TreeContent[] ? TreeContent[DataKey] extends Data ? {} : never : never ) { id: string; children: NodeModel<Data>[]; childrenKey: ChildKey; dataKey: DataKey; labelKey: LabelKey; data: Data; constructor( treeContent: TreeContent, _childrenKey: ChildKey, _dataKey: DataKey, _labelKey: LabelKey, _id = uuidv4() ) { this.dataKey = _dataKey; this.childrenKey = _childrenKey; this.labelKey = _labelKey; this.children = []; this.id = _id; if ( !(_childrenKey in treeContent) || !Array.isArray(treeContent[this.childrenKey]) ) { throw Error(`Could not find data in ${String(this.childrenKey)}`); } const children = treeContent[this.childrenKey]; // 现在TypeScript可正确识别children为TreeContent[]类型,支持数组方法调用 children.forEach(child => { this.children.push(new NodeModel(child[this.dataKey])); }); } }
方式二:使用where关键字约束(TypeScript 4.7+)
export class Tree< TreeContent, ChildKey extends keyof TreeContent, DataKey extends keyof TreeContent, Data = TreeContent[DataKey], LabelKey extends keyof Data = keyof Data > // 直接约束ChildKey对应值为TreeContent数组,DataKey对应值为Data where TreeContent[ChildKey] extends TreeContent[] where TreeContent[DataKey] extends Data { // 类实现与上述一致 }
修改后,TypeScript会基于泛型约束自动推断treeContent[this.childrenKey]的数组类型,无需额外类型断言,同时保证TreeContent[DataKey]与泛型Data的类型一致性。
内容的提问来源于stack exchange,提问作者Tim Medcalf
相关产品推荐
相关产品推荐

