打字稿:将函数的参数约束为与特定类型的值关联的对象的键
Typescript: constrain argument of function to be a key of an object associated with a value of a particular type
有没有办法进行以下类型检查?
function getNumberFromObject<T>(obj: T, key: keyof T): number {
return obj[key] // ERROR: obj[key] might not be a number
}
我想指定 key
不仅是 T
的键,而且是具有 number
值的键。
执行此操作的最直接方法,以便调用者和 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"'.
,这对开发人员更友好。不过,不确定签名的额外复杂性是否值得。
希望对您有所帮助。祝你好运!
更新:后一个函数returns 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
更具体。如果您真的想将函数的 return 类型扩展为 number
,您可以...尽管实现会对此犹豫不决,因为编译器不够聪明,无法意识到 T[K]
在一般情况下可分配给 number
。有很多方法可以解决这个问题,但最简单的方法是在实现中使用类型断言 (return obj[key] as any as number
)。
最佳解决方案:
const getNumberFromObject = <T extends Record<K, number>, K extends keyof T>(
obj: T,
key: K,
) => {
return obj[key];
};
有没有办法进行以下类型检查?
function getNumberFromObject<T>(obj: T, key: keyof T): number {
return obj[key] // ERROR: obj[key] might not be a number
}
我想指定 key
不仅是 T
的键,而且是具有 number
值的键。
执行此操作的最直接方法,以便调用者和 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"'.
,这对开发人员更友好。不过,不确定签名的额外复杂性是否值得。
希望对您有所帮助。祝你好运!
更新:后一个函数returns 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
更具体。如果您真的想将函数的 return 类型扩展为 number
,您可以...尽管实现会对此犹豫不决,因为编译器不够聪明,无法意识到 T[K]
在一般情况下可分配给 number
。有很多方法可以解决这个问题,但最简单的方法是在实现中使用类型断言 (return obj[key] as any as number
)。
最佳解决方案:
const getNumberFromObject = <T extends Record<K, number>, K extends keyof T>(
obj: T,
key: K,
) => {
return obj[key];
};