嵌套泛型中索引访问的TypeScript类型错误求助
Hey,我来帮你捋捋这个泛型类型错误的问题~
先回顾下你的场景:你有一个结构化的数据库配置对象,还有对应的TypeScript类型定义,想要实现一个函数,传入表名、列名数组和列的属性名,返回列名到对应属性值的映射。但在循环赋值那一行,TypeScript报错说Type 'TColPropName' cannot be used to index type 'TDbTblColObjRecord[TDColJsName]',咱们来拆解下问题出在哪。
错误原因分析
你写的两个版本函数,核心问题都是泛型参数的拆分破坏了类型之间的关联:
关于Version 1
你定义了多个泛型参数来拆分DbConfig的嵌套结构,但TypeScript没办法自动关联TDbTblColObjRecord[TDColJsName](某一列的类型)和TColPropName(列的属性名)之间的约束。虽然你知道所有列都符合Column接口,但泛型层面没有明确告诉TypeScript:TColPropName一定是当前列类型的有效键,所以它会担心你传入的属性名可能不匹配某个列的类型。
关于Version 2
这个版本把列属性约束成了keyof TDbColObj,但TDbColObj是所有列类型的联合类型,keyof联合类型只会保留所有成员共有的属性(这里虽然Column的属性都是共有的),但返回类型的定义太宽泛,而且循环里的类型关联依然没有被明确建立,所以还是会有类型隐患。
解决方案:简化泛型,明确类型关联
咱们可以利用DbConfig的已知结构,直接绑定列类型到Column接口,让TypeScript清晰追踪到类型之间的依赖关系。这里有两种更严谨的实现方式:
方案1:返回包含传入列名的映射(更精准)
function getColKeyToPropMap< TTable extends keyof DbConfig, TCol extends keyof DbConfig[TTable]["columns"], TProp extends keyof Column >( tbl: TTable, cols: TCol[], colPropName: TProp ): Record<TCol, Column[TProp]> { const tblInfo = simpleDbConfig[tbl].columns; const resObj = {} as Record<TCol, Column[TProp]>; for (const col of cols) { resObj[col] = tblInfo[col][colPropName]; // 这里类型完全匹配,不会报错 } return resObj; }
方案2:返回整个表的列属性映射(如果需要)
如果你的场景需要返回表中所有列的属性映射,也可以调整成这样:
function getColKeyToPropMap< TTable extends keyof DbConfig, TProp extends keyof Column >( tbl: TTable, colPropName: TProp ): Record<keyof DbConfig[TTable]["columns"], Column[TProp]> { const tblInfo = simpleDbConfig[tbl].columns; const resObj = {} as Record<keyof DbConfig[TTable]["columns"], Column[TProp]>; for (const col of Object.keys(tblInfo) as Array<keyof typeof tblInfo>) { resObj[col] = tblInfo[col][colPropName]; } return resObj; }
验证示例调用
用你给出的测试调用:
const test = getColKeyToPropMap("firstTable", ["foo"], "nameDb");
此时test的类型会被推断为Record<"foo", string>,实际运行值是{foo: "Foo"},完全符合你的预期。
核心思路总结
原来的泛型参数拆分太细,反而让TypeScript丢失了类型之间的关联信息。通过直接绑定到Column接口,我们明确告诉TypeScript:所有列都拥有nameDb和dataType这些属性,这样索引访问的类型校验就会顺利通过啦~
备注:内容来源于stack exchange,提问作者Steffen

