TypeScript如何定义需包含泛型变量对应键的对象类型
结论
该需求可以在TypeScript中实现,且完全兼容noUncheckedIndexAccess: true配置,不会出现索引访问被判定为可能未定义的问题。
实现代码
基础版本(手动指定对象类型联合)
interface One { foo: string; bar: string; } interface Two { bar: string; foobar: string; } interface Three { foobar: string; foo: string; } // 约束传入对象必须包含T作为必填键 type SomeType<T extends PropertyKey> = { [K in T]: unknown; } & Record<PropertyKey, unknown>; /** * @param object 必须包含key对应字段的对象 * @param key 字符串字面量类型的键名 */ function fn<T extends PropertyKey, O extends SomeType<T>>(object: O, key: T) { // 此处访问无类型报错,TS可判定key一定是object的合法键 const val = object[key]; // 其余业务逻辑 } // 调用示例 const one: One = { foo: "1", bar: "1" }; const two: Two = { bar: "2", foobar: "2" }; const three: Three = { foobar: "3", foo: "3" }; fn<"foo", One | Three>(one, "foo"); // 校验通过 fn<"foo", One | Three>(three, "foo"); // 校验通过 fn<"foo", One | Three>(two, "foo"); // 类型报错:two缺少必填键foo fn<"bar", One | Two>(one, "bar"); // 校验通过 fn<"bar", One | Two>(two, "bar"); // 校验通过 fn<"bar", One | Two>(three, "bar"); // 类型报错:three缺少必填键bar
优化版本(自动筛选符合键要求的类型)
如果不需要手动传入对象联合类型,可以提前将所有候选接口合并为联合类型,通过条件类型自动筛选出包含指定键的类型,调用时仅需传入键名泛型即可:
// 所有待校验候选接口的联合集合 type AllCandidates = One | Two | Three; // 从候选集合中筛选出包含K键的类型 type MatchKey<K extends PropertyKey> = AllCandidates extends infer O ? O extends Record<K, any> ? O : never : never; function fnOpt<T extends PropertyKey>(object: MatchKey<T>, key: T) { const val = object[key]; // 索引访问依然安全,无noUncheckedIndexAccess报错 } // 调用示例 fnOpt<"foo">(one, "foo"); // 校验通过,自动推导object类型为One | Three fnOpt<"foo">(three, "foo"); // 校验通过 fnOpt<"foo">(two, "foo"); // 类型报错 fnOpt<"bar">(one, "bar"); // 校验通过,自动推导object类型为One | Two fnOpt<"bar">(two, "bar"); // 校验通过 fnOpt<"bar">(three, "bar"); // 类型报错
实现要点
SomeType<T>通过映射类型强制要求传入对象必须存在T对应的键,缺键的类型会直接触发类型校验失败- 基础版本通过双泛型约束
O extends SomeType<T>,让TypeScript能够明确判定T一定是对象O的合法键,因此开启noUncheckedIndexAccess时,object[key]不会被标记为可能为undefined - 优化版本通过分布式条件类型,从预定义的候选类型联合中自动过滤出包含指定键的类型,调用时不需要手动传入对象类型联合,更符合使用预期
内容的提问来源于stack exchange,提问作者Michael.Lumley
相关产品推荐
相关产品推荐

