打字稿:将函数的参数约束为与特定类型的值关联的对象的键

时间:2018-09-05 15:08:11

标签: typescript generics keyof

是否可以进行以下类型检查?

function getNumberFromObject<T>(obj: T, key: keyof T): number {
  return obj[key] // ERROR: obj[key] might not be a number
}

我想指定key不仅应该是T的键,而且应该是具有number值的键。

1 个答案:

答案 0 :(得分:3)

执行此操作以使调用方和getNumberFromObject<T>类型实现正确检查的最直接方法是:

function getNumberFromObject<T extends Record<K, number>, K extends keyof any>(
  obj: T, 
  key: K
): number {
  return obj[key] // okay
}

当您调用它时:

getNumberFromObject({dog: 2, cat: "hey", moose: 24}, "dog"); // okay
getNumberFromObject({dog: 2, cat: "hey", moose: 24}, "cat"); // error
getNumberFromObject({dog: 2, cat: "hey", moose: 24}, "moose"); // okay
getNumberFromObject({dog: 2, cat: "hey", moose: 24}, "squirrel"); // error

一切正常,除了您得到的错误有点晦涩,因为它抱怨Object literal may only specify known properties, and 'dog' does not exist in type 'Record<"somebadkey", number>'。此投诉是excess property check,并不是真正的问题。

如果您想这样做,以使呼叫者得到更好的错误,则可以使用更复杂的conditional type,如下所示:

function getNumberFromObject<T, K extends keyof any & {
  [K in keyof T]: T[K] extends number ? K : never
}[keyof T]>(
  obj: T,
  key: K
): T[K] {
  return obj[key] // okay
}

getNumberFromObject({ dog: 2, cat: "hey", moose: 24 }, "dog"); // okay
getNumberFromObject({ dog: 2, cat: "hey", moose: 24 }, "cat"); // error
getNumberFromObject({ dog: 2, cat: "hey", moose: 24 }, "moose"); // okay
getNumberFromObject({ dog: 2, cat: "hey", moose: 24 }, "squirrel"); // error

在这种情况下,T是不受约束的,但是K被强制为T中的那些键,其中T[K]number

现在错误显示Argument of type '"somebadkey"' is not assignable to parameter of type '"dog" | "moose"'.,这对开发人员更友好。不过,不确定签名的额外复杂性是否值得。

希望有帮助。祝你好运!


更新:后一个函数返回T[K],而不是number。这可能是一件好事,因为T[K]可能比number更具体。例如:

interface Car {
  make: string,
  model: string,
  horsepower: number,
  wheels: 4
}
declare const car: Car;
const four = getNumberFromObject(car, 'wheels'); // 4, not number

four的类型为4,比number更具体。如果您确实想将函数的返回类型扩展为number,则可以...尽管由于编译器不够聪明,无法意识到T[K]可分配给number(在一般情况下)。有很多方法可以解决,但是最简单的方法是在实现(return obj[key] as any as number)中使用类型断言。