TypeScript三级属性类型索引报错:Type cannot be used to index type
在为常量的三级属性定义类型时,出现了意外的索引错误。尽管类型实际使用时行为符合预期,但定义SelectedActorSelectedOptionSections时,TypeScript提示Type '"sections"' cannot be used to index type 'SelectedActorSelectedOption<T, O>',需要消除该报错。
复现代码
interface ActorOptionSection<K extends string = string> { key: K; } interface ActorOption<K extends string = string> { key: K; sections: Record<string, ActorOptionSection<string>>; } interface Actor<K extends string = string> { key: K; options: Record<string, ActorOption<string>>; } const ActorMap = { alice: { key: "alice", options: { optA1: { key: "optA1", sections: { sectionA11: { key: "sectionA11" } } }, optA2: { key: "optA2", sections: { sectionA21: { key: "sectionA21" } } } } }, bob: { key: "bob", options: { optB1: { key: "optB1", sections: { sectionB11: { key: "sectionB11", } } }, optB2: { key: "optB2", sections: { sectionB21: { key: "sectionB21", } } } } } } as const satisfies Record<string, Actor<string>>; type TypeOfMap = typeof ActorMap; type ActorKey = keyof typeof ActorMap; type SelectedActor<T extends ActorKey> = TypeOfMap[T]; type SelectedActorOptions<T extends ActorKey> = SelectedActor<T>["options"]; type KeyOfSelectedActorOptions<T extends ActorKey> = keyof SelectedActorOptions<T>; type SelectedActorSelectedOption<T extends ActorKey, O extends KeyOfSelectedActorOptions<T>> = SelectedActorOptions<T>[O]; type SelectedActorSelectedOptionSections<T extends ActorKey, O extends KeyOfSelectedActorOptions<T>> = SelectedActorSelectedOption<T, O>["sections"] // Unexpected error: Type '"sections"' cannot be used to index type 'SelectedActorSelectedOption<T, O>'.(2536) // No error (as expected) const aliceOptA1Sections: SelectedActorSelectedOptionSections<"alice", "optA1"> = { sectionA11: { key: "sectionA11" } }; // Error (as expected): Object literal may only specify known properties, but 'sectionA11' does not exist in type '{ readonly sectionA21: { readonly key: "sectionA21"; }; }'. Did you mean to write 'sectionA21'?(2561) const aliceOptA2Sections: SelectedActorSelectedOptionSections<"alice", "optA2"> = { sectionA11: { key: "sectionA11" } };
问题原因
TypeScript无法在泛型层面确认SelectedActorSelectedOption<T, O>一定包含sections属性。虽然Actor系列接口明确了ActorOption拥有sections,但as const生成的具体只读类型与接口的关联,在泛型推导中没有被显式传递,导致编译器无法确定该属性的存在。
解决方案
方案1:为SelectedActorSelectedOption添加显式接口约束
修改SelectedActorSelectedOption的定义,通过交叉类型明确它符合ActorOption<string>结构,让编译器确认sections属性存在:
type SelectedActorSelectedOption<T extends ActorKey, O extends KeyOfSelectedActorOptions<T>> = SelectedActorOptions<T>[O] & ActorOption<string>;
修改后,SelectedActorSelectedOptionSections的索引操作就不会再报错,同时保留原有的具体字面量类型检查。
方案2:直接基于接口结构推导类型
如果不需要严格保留ActorMap的具体字面量类型,可直接从Actor接口出发定义类型:
type SelectedActorSelectedOptionSections<T extends ActorKey, O extends keyof Actor<string>['options']> = Actor<string>['options'][O]['sections'];
但此方法会丢失ActorMap中具体的键(如optA1、sectionA11)的类型提示,仅保留接口定义的结构,适合不需要依赖具体常量值的场景。
效果验证
修改后,SelectedActorSelectedOptionSections的定义不再报错,同时使用时依然能正确校验属性:
- 正确赋值无报错
- 传入不存在的section键时,依然会触发预期的类型错误
内容的提问来源于stack exchange,提问作者DoronG

