能否声明带类型的关联数组并将其用作键类型化对象?
实现键类型化的递归关联数组(TypeScript)
问题背景
你定义了如下递归关联数组类型,但使用时无法通过具体键(如key1、subKey1)获取属性——因为AssociativeArrayT使用了字符串索引签名,TypeScript无法推断对象的具体键名,导致类型提示丢失:
type CellT = { name: string; value: number }; type AssociativeArrayT = { [key: string]: CellT[] | AssociativeArrayT; }; const myObject: AssociativeArrayT = { key1: { subKey1: [ { name: 'SomeName1', value: 100 }, { name: 'SomeName2', value: 100 }, ], }, key2: [ { name: 'SomeName3', value: 300 }, { name: 'SomeName4', value: 400 }, ], };
需求是:既能用类型校验对象结构(值只能是CellT[]或子关联数组),又能保留对象的具体键类型,支持通过键直接访问属性。
解决方案
可以通过泛型递归类型实现,以下提供两种实用方案:
方案1:提前定义结构类型(适合明确约束场景)
通过泛型递归类型约束结构,同时保留具体键的类型信息:
type CellT = { name: string; value: number }; // 泛型递归类型:约束每个键的值要么是CellT数组,要么是子关联数组 type AssociativeArray<T extends Record<string, any>> = { [K in keyof T]: T[K] extends CellT[] ? CellT[] : T[K] extends Record<string, any> ? AssociativeArray<T[K]> : never; }; // 定义具体的对象结构类型 type MyObjectSchema = AssociativeArray<{ key1: { subKey1: CellT[]; }; key2: CellT[]; }>; // 初始化对象,既受结构校验,又保留键类型 const myObject: MyObjectSchema = { key1: { subKey1: [ { name: 'SomeName1', value: 100 }, { name: 'SomeName2', value: 100 }, ], }, key2: [ { name: 'SomeName3', value: 300 }, { name: 'SomeName4', value: 400 }, ], }; // 正常访问具体键,类型提示完整 console.log(myObject.key1.subKey1[0].value); // 类型推断为number
方案2:自动推断字面量类型(灵活初始化场景)
用工具函数结合递归约束类型,直接从对象字面量推断键类型,同时校验结构:
type CellT = { name: string; value: number }; // 递归约束:定义合法的关联数组结构 type ValidAssociativeArray = | CellT[] | { [key: string]: ValidAssociativeArray }; // 工具函数:接收符合约束的对象,返回其字面量类型 const createAssociativeArray = <T extends ValidAssociativeArray>(obj: T) => obj; // 初始化时调用工具函数,自动推断键类型并校验结构 const myObject = createAssociativeArray({ key1: { subKey1: [ { name: 'SomeName1', value: 100 }, { name: 'SomeName2', value: 100 }, ], }, key2: [ { name: 'SomeName3', value: 300 }, { name: 'SomeName4', value: 400 }, ], }); // 支持直接访问具体键,类型提示准确 myObject.key1.subKey1; // 类型为CellT[] myObject.key2; // 类型为CellT[]
方案对比
- 方案1:需提前定义结构类型,适合需要明确对外暴露接口的场景,结构约束更严格。
- 方案2:无需提前定义结构,直接从字面量推断,初始化更灵活,适合快速开发场景。
内容的提问来源于stack exchange,提问作者Kirill Kazakov
相关产品推荐
相关产品推荐

