TypeScript:接受映射到特定类型的所有对象键

Sim*_*n H 3 types typescript

给定一个对象类型(或类类型),我想编写一个接受该对象及其键列表的函数。但是,我只想允许映射到特定类型值的键,例如仅字符串。

例子:

function shouldOnlyAcceptStringValues(o, key) {
    // Do something with o[key] that depends on the assumption that o[key] has a specific type, e.g. string
}

const obj = {
    a: 1,
    b: "test",
    c: "bla"
}

const key = "c" as const;
shouldOnlyAcceptStringValues(obj, key);  // b and c should be accepted as keys, a not.
Run Code Online (Sandbox Code Playgroud)

我知道一种强制执行key实际存在的方法o(无论 的类型如何o[key]):

function shouldOnlyAcceptStringValues<T>(o: T, key: keyof T) {
    // Do something with o[key] that depends on the assumption that o[key] has a specific type, e.g. string
}
Run Code Online (Sandbox Code Playgroud)

但是,这也允许使用key="a"映射到数字的虽然。

我需要的是这样的:

function shouldOnlyAcceptStringValues<T, K extends keyof T, T[K] extends string>(o: T, key: K)
Run Code Online (Sandbox Code Playgroud)

但这当然不是有效的 TypeScript 代码。

有什么技巧可以使它起作用吗?我需要一种方法来进一步完善密钥集keyof T。函数体应该知道这o[key]是一个字符串,而无需显式检查函数内部的类型。这有可能吗?

jca*_*alz 8

如果您想要从调用者的角度和从实现者的角度来看都有效的东西,您可以这样做:

function shouldOnlyAcceptStringValues<K extends PropertyKey>(
  o: Record<K, string>, key: K
) {
    const okay: string = o[key];
}
Run Code Online (Sandbox Code Playgroud)

这有点向后看你的约束;不是限制key为 from 的正确键obj,而是限制obj为值类型 atkey为 a的对象string。您可以看到它okay被接受为 a string,并且在调用方方面也可以正常工作:

shouldOnlyAcceptStringValues(obj, "a"); // error!
// ------------------------> ~~~
// Argument of type '{ a: number; b: string; c: string; }' is 
// not assignable to parameter of type 'Record<"a", string>'.

shouldOnlyAcceptStringValues(obj, "b"); // okay
shouldOnlyAcceptStringValues(obj, "c"); // okay
Run Code Online (Sandbox Code Playgroud)

唯一的障碍是第一次调用的错误可能不是你期望的参数;它在抱怨obj而不是"a"。如果可以,那就太好了。如果没有,那么您可以将调用签名更改为您正在谈论的那种约束:


type KeysMatching<T, V> = { [K in keyof T]: T[K] extends V ? K : never }[keyof T]
function shouldOnlyAcceptStringValues2<T>(o: T, key: KeysMatching<T, string>): void;
function shouldOnlyAcceptStringValues2<K extends PropertyKey>(
  o: Record<K, string>, key: K
) {
    const okay: string = o[key];
}
Run Code Online (Sandbox Code Playgroud)

KeysMatching<T, V>类型的函数需要一个类型T并返回只是那些键,其值分配给V。因此调用签名将指定TforoKeysMatching<T, string>for key。请注意我是如何将调用签名编写为单个重载的,并且实现签名与以前相同。如果你不这样做,那么编译器是无法理解,对于一般的TT[KeysMatching<T, string>]是分配给string; 这是编译器无法进行的高阶类型推断:

function shouldOnlyAcceptStringValuesOops<T>(o: T, key: KeysMatching<T, string>) {
    const oops: string = o[key]; // error!
    // -> ~~~~
    // Type 'T[{ [K in keyof T]: T[K] extends string ? K : never; }[keyof T]]' 
    // is not assignable to type 'string'. 
}
Run Code Online (Sandbox Code Playgroud)

因此,在重载版本中,我们让调用者看到 上的约束,key实现看到 上的约束obj,这对每个人都更好:

shouldOnlyAcceptStringValues2(obj, "a"); // error!
// ------------------------------> ~~~
// Argument of type '"a"' is not assignable to parameter of type '"b" | "c"'

shouldOnlyAcceptStringValues2(obj, "b"); // okay
shouldOnlyAcceptStringValues2(obj, "c"); // okay
Run Code Online (Sandbox Code Playgroud)

现在编译器抱怨key而不是obj.


好的,希望有帮助;祝你好运!

Playground 链接到代码