这是人为的,但是通常我可以将对象文字传递给函数,并以通用的方式捕获文字的值,即:
type Values<V> = {
a: V;
b: V;
};
function mapValues<V>(v: Values<V>): V {
return v as any; // ignore
}
const vn = mapValues({ a: 1, b: 2 }); // inferred number
const vs = mapValues({ a: '1', b: '2' }); // inferred string
根据我传递给vn
的内容,vs
和number
都正确地推断为string
或mapValues
。
这甚至适用于索引类型:
function mapValues2<V>(v: { [key: string]: V }): V {
return v as any;
}
const v2n = mapValues2({ a: 1, b: 2 }); // inferred number
const v2s = mapValues2({ a: '1', b: '2' }); // inferred string
v
对象文字知道它们的值分别是string
/ number
,并且我能够捕获推断出的V
并在返回中使用它类型。
但是,一旦我使用了映射类型,即:
enum Foo {
a,
b,
}
function mapValues3<K, V>(o: K, v: { [key in keyof K]: V }): V {
return v as any;
}
const v3n = mapValues3(Foo, { a: 1, b: 2 }); // inferred unknown
const v3s = mapValues3(Foo, { a: '1', b: '2' }); // inferred unknown
就像V
分别忘记了它是string
/ number
一样,我推断出unknown
。
请注意,如果我明确键入const v3n: number
则可以,但是我想依靠类型推断为我找出V
。
我很困惑为什么将[key: string]
从第二个片段更改为第三个[key in keyof K]
会影响对象文字类型推断的: V
一侧的推断。
有什么想法吗?
答案 0 :(得分:1)
对于这种失败的原因,我没有一个规范的答案。我的直觉是,您正在尝试使mapValue3(o, v)
参数的键和值都取决于v
参数的键,从而推论o
参数的键和值,从而推迟{ {1}},直到为时已晚,编译器放弃了一般的V
推论。
通常情况下,您的值unknown
的类型为val
,并试图从中推断出一个相关的类型U
。也就是说,您将T
视为某个类型函数U
的{{1}},并且希望编译器从F<T>
推断F
。假设这样的推论甚至对任何人都是可行的(像T
这样的有损函数会丢弃信息,因此您无法从F<T>
来推断type F<T> = T extends object ? true : false
),这就是对于编译器并非总是可行。如果可行,那就太好了。
否则,我的经验法则是:直接从T
类型的值推断F<T>
而不是从类型T
的值推断U
。然后,将某个类型函数U
的{{1}}表示为T
。也就是说,G<U>
类型的函数应该是G
函数的 inverse 。 (因此,对于所有G
,F
是G<F<T>>
)。逆类型函数T
的编写可能比T
更为复杂,但是如果是这样,那么人类可能会比编译器更好。另外请注意,您可能需要constrain的G
类型为F
才能使映射起作用。
以上内容也适用于多种类型的变量(例如,类型为U
,F<any>
,{{1}的值v1
,v2
,v3
},并且您想从中推断出U1 = F1<T1, T2, T3>
,U2 = F2<T1, T2, T3>
和U3 = F3<T1, T2, T3>
:相反,找到T1
,T2
和T3
使得G1
,G2
和G3
并直接进行计算)。
让我们为您的函数做一下,就像这样重写:
T1 = G1<U1, U2, U3>
我们想将其转换为此:
T2 = G2<U1, U2, U3>
请注意,由于第一个参数已经是“推断此参数的值”,因此对于T3 = G3<U1, U2, U3>
,我们不需要做任何事情。问题是:function mapValues3<O, T>(o: O, v: F<O, T>): T {
return null!
}
type F<O, T> = Record<keyof O, T>; // same as { [key in keyof O]: T };
是什么?给定类型function mapValues3<O, U extends F<O, any>>(o: O, v: U): G<O, U> {
return null!
}
type G<O, U> = ???;
等于O
的值,我们如何得出G
?答案是使用lookup type:
U
我们只需确保:Record<keyof O, T>
是T
,它是type G<O, U> = U[keyof O];
,其结果是G<O, Record<keyof O, T>
。我们现在可以消除Record<keyof O, T>[keyof O]
和{[K in keyof O]: T}[keyof O]
并给您:
T
让我们对其进行测试:
F
这些按您想要的方式工作。我们还来看一些可能的边缘情况:
G
这看起来不错,因为您希望第二个参数具有第一个参数中的所有键。这:
function mapValues3<O, U extends Record<keyof O, any>>(o: O, v: U): U[keyof O] {
return null!
}
这可能会也可能不会。它没有违反约束。它只是具有额外的属性,通常在TypeScript中是允许的(但是forbidden in some situations)。推论是const v3n = mapValues3(Foo, { a: 1, b: 2 }); // number
const v3s = mapValues3(Foo, { a: "1", b: "2" }); // string
而不是const constraintViolation = mapValues3(Foo, { a: "hey" }); // error! "b" is missing
,因为在计算返回类型时不会参考额外的属性。如果有问题,可以通过多种方法使const excessProp = mapValues3(Foo, { a: 1, b: 2, c: false }); // number, no error
拒绝此类事情,但是它们更加复杂,这不是问题的一部分。
好的,希望能有所帮助。祝你好运!