给定一个对象类型(或类类型),我想编写一个接受该对象及其键列表的函数。但是,我只想允许映射到特定类型值的键,例如仅字符串。
例子:
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]是一个字符串,而无需显式检查函数内部的类型。这有可能吗?
如果您想要从调用者的角度和从实现者的角度来看都有效的东西,您可以这样做:
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。因此调用签名将指定Tforo和KeysMatching<T, string>for key。请注意我是如何将调用签名编写为单个重载的,并且实现签名与以前相同。如果你不这样做,那么编译器是无法理解,对于一般的T说T[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.
好的,希望有帮助;祝你好运!