我正在编写一个函数,该函数将从对象中的指定键返回固定类型。因此该函数总是返回类型 string
,但对于通用对象,您必须指定一个包含字符串的键:
type KeyFor<T, O, K extends keyof O> = O[K] extends T ? K : never;
function selectFromValue<O, K extends keyof O>(obj: O, path: KeyFor<string, O, K>) {
}
这似乎适用于以下示例:
interface Example {
x: string,
y: number,
};
const example: Example = {
x: '',
y: 7,
};
selectFromValue(example, 'x'); // Compiles, as expected.
selectFromValue(example, 'y'); // Errors, as expected.
然而,我想要第一个参数作为查找函数,而不是一个简单的值。其他类型相同:
function selectFromFunction<O, K extends keyof O>(get: () => O, path: KeyFor<string, O, K>) {
}
selectFromValue(() => example, 'x'); // Still working, because why wouldn't it?
但是当我向查找函数添加一个参数的那一刻,即使什么都不改变,它也会中断:
function selectWithParameter<O, K extends keyof O>(get: (state) => O, path: KeyFor<string, O, K>) {
}
selectWithParameter((arg) => example, 'x');
我尝试了带有额外泛型类型参数的 (state: any)
、(state: unknown)
和 (state: A)
,它们都给出:
Argument of type 'string' is not assignable to parameter of type 'never'.
在'x'
。它工作的唯一方法是,如果 (arg)
本身被赋予一个显式类型,但我不明白为什么这会对类型检查 P[K] extends string
产生影响,因为它是一个完全不相关的类型:(arg: number) => example
.
所以,在写这个问题时,我发现了以下问题:
Typescript generics: given key K and object T, constrain the type of T[K]
这让我可以将代码重写为:
function selectFromValue<O extends Record<K, string>, K extends string>(obj: O, path: K) {
}
function selectFromFunction<O extends Record<K, string>, K extends string>(obj: () => O, path: K) {
}
function selectWithParameter<O extends Record<K, string>, K extends string>(get: (state) => O, path: K) {
}
interface Example {
x: string,
y: number,
};
const example: Example = {
x: '',
y: 7,
};
selectFromValue(example, 'x');
selectFromFunction(() => example, 'x');
selectWithParameter((arg) => example, 'x');
这完全符合我的预期,所以我将使用它,但我仍然想知道原始版本失败的原因。