TypeScript 类型推断与通用类型的交集
TypeScript Type Inference with Intersection of Generic Types
我有一个用例,其中我有一个可组合对象,它应该(不)与某些功能一起使用,具体取决于它的组成部分。组合是使用类型交集完成的。
这是代码的简化版本:
type A<T> = { a: T }
type B<T> = { b: (val: T) => T }
const shouldWork = {
a: 'str',
b: (val: string) => val.toUpperCase(),
someOtherProp: 'foo'
}
const shouldFail = {
a: 'str',
b: (val: number) => 42
}
function test<T extends A<???> & B<???>>(obj: T): T {
return {
...obj,
a: obj.b(obj.a)
}
}
const res1 = test(shouldWork);
// res1 should be typeof shouldWork, i.e. A & B & { someOtherProp: string }
console.log(res1.a); // "STR"
console.log(res1.someOtherProp); // "foo"
const res2 = test(shouldFail); // should fail because A<string> and B<number> don't match
我试过使用 test<T extends A<any> & B<any>>
,但这当然会允许泛型类型的任意组合。
然后我尝试添加一个额外的类型 S
以确保两个泛型相同:test<S, T extends A<S> & B<S>>
。但这会失败,因为 "参数类型 'val' 和 'val' 不兼容。类型 'unknown' 不可分配给类型 'string'"
经过更多尝试后,我发现了一些可以按预期工作的东西:
function test<S, T extends A<S> & B<S> = A<S> & B<S>>(obj: T): T { ... }
const res1 = test<string>(shouldWork);
const res2 = test<string>(shouldFail); // throws error: Types of parameters 'val' and 'val' are incompatible. Type 'string' is not assignable to type 'number'
但是如你所见,读起来很困难,写起来也很困难,尤其是当交集包含更多类型时。
是否有更简单的方法来完成此操作?
这有点“不确定”,但是实现您想要的推理的一种方法是这样的:
function test<O, T>(obj: O & A<T> & B<T>): O {
return {
...obj,
a: obj.b(obj.a)
}
}
当您希望编译器从对函数 func(param)
的调用中推断类型参数 X
时,其中 param
的类型为 P
且调用签名为func
类似于 <X>(param: F<X>) => any
,编译器将为 F<X>
插入 P
以获得 X
.
的候选项
如果调用签名涉及 intersection type,如 <X>(param: F<X> & G<X>) => void
,编译器将倾向于为 both [=21] 插入 P
=] 和 G<X>
来获得候选人......即使只为其中一个人这样做是好的(有时是可取的)。
因此,对于 test(obj)
的调用签名 <O, T>(obj: O & A<T> & B<T>) => O
,编译器将倾向于将 O
与 typeof obj
匹配以获得 O
的候选对象,并且然后还匹配 A<T>
和 B<T>
到 typeof obj
以获得 T
的候选。如果 obj
不是有效的 A<T> & B<T>
,推理将失败。您可以使用 O
来表示 obj
的实际类型,而无需将其扩展为 A<T> & B<T>
.
所以这会按照您想要的方式运行:
const res1 = test(shouldWork); // okay
const res2 = test(shouldFail); // error
// -------------> ~~~~~~~~~~
// Argument of type '{ a: string; b: (val: number) => number; }' is not assignable to
// parameter of type '{ a: string; b: (val: number) => number; } & A<number> & B<number>'.
如果这不起作用,您可以通过使用单个不受约束的类型参数 O
并使参数 obj
成为 conditional type 来获得类似的行为:
function test<O>(obj: O extends (A<infer T> & B<infer T>) ? O : never): O {
return {
...obj,
a: obj.b(obj.a)
}
}
const res1 = test(shouldWork); // okay
const res2 = test(shouldFail); // error\
// -------------> ~~~~~~~~~~
// Argument of type '{ a: string; b: (val: number) => number; }' \
// is not assignable to parameter of type 'never'
这是可行的,因为编译器倾向于将参数 P
的类型插入到条件类型 X
中,例如 X extends ...
,因此 typeof obj
是 O
的候选者,然后根据 A<infer T> & B<infer T>
进行检查。如果找到有效的 T
,则 obj
将再次与 O
进行检查,这没有问题。如果未找到有效的 T
,则将根据 never
检查 obj
,这可能不会起作用。它有点复杂,但具有相似的行为(错误消息不太容易理解)。
至于以下为什么不起作用:
declare function test<O extends A<T> & B<T>, T>(obj: O): O;
问题是T
没有推理站点。编译器可以使用 typeof obj
来推断 O
,但是当它检查 T
时,它被卡住了。您可能希望编译器可以从 O
的 constraint A<T> & B<T>
, but generic constraints are not used as inference sites this way. See microsoft/TypeScript#7234 for an old suggestion to support this. Instead of implementing such a feature, they suggested 中推断出 T
人们从交叉点推断出的方式与我在本答案开头所做的完全相同。
无论如何,T
没有推理站点,编译器会回退到 unknown
。然后 O
被限制为 A<unknown> & B<unknown>
,这不太可能起作用。所以一切都崩溃了。
我有一个用例,其中我有一个可组合对象,它应该(不)与某些功能一起使用,具体取决于它的组成部分。组合是使用类型交集完成的。
这是代码的简化版本:
type A<T> = { a: T }
type B<T> = { b: (val: T) => T }
const shouldWork = {
a: 'str',
b: (val: string) => val.toUpperCase(),
someOtherProp: 'foo'
}
const shouldFail = {
a: 'str',
b: (val: number) => 42
}
function test<T extends A<???> & B<???>>(obj: T): T {
return {
...obj,
a: obj.b(obj.a)
}
}
const res1 = test(shouldWork);
// res1 should be typeof shouldWork, i.e. A & B & { someOtherProp: string }
console.log(res1.a); // "STR"
console.log(res1.someOtherProp); // "foo"
const res2 = test(shouldFail); // should fail because A<string> and B<number> don't match
我试过使用 test<T extends A<any> & B<any>>
,但这当然会允许泛型类型的任意组合。
然后我尝试添加一个额外的类型 S
以确保两个泛型相同:test<S, T extends A<S> & B<S>>
。但这会失败,因为 "参数类型 'val' 和 'val' 不兼容。类型 'unknown' 不可分配给类型 'string'"
经过更多尝试后,我发现了一些可以按预期工作的东西:
function test<S, T extends A<S> & B<S> = A<S> & B<S>>(obj: T): T { ... }
const res1 = test<string>(shouldWork);
const res2 = test<string>(shouldFail); // throws error: Types of parameters 'val' and 'val' are incompatible. Type 'string' is not assignable to type 'number'
但是如你所见,读起来很困难,写起来也很困难,尤其是当交集包含更多类型时。
是否有更简单的方法来完成此操作?
这有点“不确定”,但是实现您想要的推理的一种方法是这样的:
function test<O, T>(obj: O & A<T> & B<T>): O {
return {
...obj,
a: obj.b(obj.a)
}
}
当您希望编译器从对函数 func(param)
的调用中推断类型参数 X
时,其中 param
的类型为 P
且调用签名为func
类似于 <X>(param: F<X>) => any
,编译器将为 F<X>
插入 P
以获得 X
.
如果调用签名涉及 intersection type,如 <X>(param: F<X> & G<X>) => void
,编译器将倾向于为 both [=21] 插入 P
=] 和 G<X>
来获得候选人......即使只为其中一个人这样做是好的(有时是可取的)。
因此,对于 test(obj)
的调用签名 <O, T>(obj: O & A<T> & B<T>) => O
,编译器将倾向于将 O
与 typeof obj
匹配以获得 O
的候选对象,并且然后还匹配 A<T>
和 B<T>
到 typeof obj
以获得 T
的候选。如果 obj
不是有效的 A<T> & B<T>
,推理将失败。您可以使用 O
来表示 obj
的实际类型,而无需将其扩展为 A<T> & B<T>
.
所以这会按照您想要的方式运行:
const res1 = test(shouldWork); // okay
const res2 = test(shouldFail); // error
// -------------> ~~~~~~~~~~
// Argument of type '{ a: string; b: (val: number) => number; }' is not assignable to
// parameter of type '{ a: string; b: (val: number) => number; } & A<number> & B<number>'.
如果这不起作用,您可以通过使用单个不受约束的类型参数 O
并使参数 obj
成为 conditional type 来获得类似的行为:
function test<O>(obj: O extends (A<infer T> & B<infer T>) ? O : never): O {
return {
...obj,
a: obj.b(obj.a)
}
}
const res1 = test(shouldWork); // okay
const res2 = test(shouldFail); // error\
// -------------> ~~~~~~~~~~
// Argument of type '{ a: string; b: (val: number) => number; }' \
// is not assignable to parameter of type 'never'
这是可行的,因为编译器倾向于将参数 P
的类型插入到条件类型 X
中,例如 X extends ...
,因此 typeof obj
是 O
的候选者,然后根据 A<infer T> & B<infer T>
进行检查。如果找到有效的 T
,则 obj
将再次与 O
进行检查,这没有问题。如果未找到有效的 T
,则将根据 never
检查 obj
,这可能不会起作用。它有点复杂,但具有相似的行为(错误消息不太容易理解)。
至于以下为什么不起作用:
declare function test<O extends A<T> & B<T>, T>(obj: O): O;
问题是T
没有推理站点。编译器可以使用 typeof obj
来推断 O
,但是当它检查 T
时,它被卡住了。您可能希望编译器可以从 O
的 constraint A<T> & B<T>
, but generic constraints are not used as inference sites this way. See microsoft/TypeScript#7234 for an old suggestion to support this. Instead of implementing such a feature, they suggested 中推断出 T
人们从交叉点推断出的方式与我在本答案开头所做的完全相同。
无论如何,T
没有推理站点,编译器会回退到 unknown
。然后 O
被限制为 A<unknown> & B<unknown>
,这不太可能起作用。所以一切都崩溃了。