使用泛型缩小类型
Types narrowing with generics
我想 link 一个函数中的 2 个泛型类型,并通过检查其中一个类型对这两种类型使用窄化。正确的做法是什么?
type A = 'A';
type B = 'B';
type AB = A | B
type ComplexType<T> = {value: T}
const f = (next: ComplexType<A>) => {}
const builder = <T extends AB>(value: T) => (next: ComplexType<T>) => {
if (value === 'A') {
f(next) // expect next is ComplexType<A> but got error
}
}
您需要让您的函数 f
也知道您的泛型。
改变
const f = (next: ComplexType<A>) => {}
至
const f = <T extends AB>(next: ComplexType<T>) => {}
应该可以。
目前无法通过检查 值 如 value
来缩小 类型参数 如 T
]. value === "A"
可能是真的,但这并不意味着 T
是 "A"
。毕竟,也许 value
是 "A" | "B"
类型,比如通过 pass 在编译器推断完整联合类型的表达式中:
builder(Math.random() <= 0.999 ? "A" : "B") // no error
此处您有 99.9% 的几率在 "A"
中通过,但您仍有可能在 "B"
中通过。编译器推断 T
是 "A" | "B"
。因此 next
参数的类型为 ComplexType<"A" | "B">
。所以当你调用这个时没有编译器错误:
builder(Math.random() <= 0.999 ? "A" : "B")({ value: "B" }); // no error
这意味着编译器在技术上是正确的 f(next)
可能是错误的。
GitHub 中存在多个问题,要求支持在通用函数体内缩小类型参数。与您的代码最相关的可能是 microsoft/TypeScript#27808。这需要一些方法来告诉编译器 T
应该是 either "A"
or "B"
并且不是 "A" | "B"
。也许语法会像 T extends_oneof [A, B]
或 (T extends A) | (T extends B)
或完全不同的东西。那么当您测试 value === "A"
时,编译器可能会得出 T extends A
的结论,并且一切正常。唉,目前还没有这样的支持。
现在你只需要解决它。如果您相当有信心没有人会错误地调用您的 builder()
,您可以使用 type assertion 并继续:
const builder = <T extends AB>(value: T) => (next: ComplexType<T>) => {
if (value === 'A') {
f(next as ComplexType<A>) // okay
}
}
如果您真的需要防止调用者使用 builder()
做错事,您可以制作越来越复杂的调用签名,相当于模拟“扩展其中之一”约束,例如:
type NoneOf<T, U extends any[]> =
[T] extends [U[number]] ? never : T;
type OneOf<T, U extends any[]> =
U extends [infer F, ...infer R] ? [T] extends [F] ? NoneOf<T, R> : OneOf<T, R> : never;
const builder = <T extends "A" | "B">(
value: T & OneOf<T, ["A", "B"]>
) => (next: ComplexType<T>) => {
if (value === 'A') {
f(next as ComplexType<"A">)
}
}
builder(Math.random() <= 0.999 ? "A" : "B"); // error now
builder2("A") // okay
builder2("B") // okay
但是编译器当然不能在 builder
的主体内遵循它(通用条件类型很难处理)所以你仍然需要类型断言。就我个人而言,我只会将您的原始签名与类型断言一起使用,如果您在实践中 运行 进入无效调用,则只会重新访问任何更复杂的内容。
我想 link 一个函数中的 2 个泛型类型,并通过检查其中一个类型对这两种类型使用窄化。正确的做法是什么?
type A = 'A';
type B = 'B';
type AB = A | B
type ComplexType<T> = {value: T}
const f = (next: ComplexType<A>) => {}
const builder = <T extends AB>(value: T) => (next: ComplexType<T>) => {
if (value === 'A') {
f(next) // expect next is ComplexType<A> but got error
}
}
您需要让您的函数 f
也知道您的泛型。
改变
const f = (next: ComplexType<A>) => {}
至
const f = <T extends AB>(next: ComplexType<T>) => {}
应该可以。
目前无法通过检查 值 如 value
来缩小 类型参数 如 T
]. value === "A"
可能是真的,但这并不意味着 T
是 "A"
。毕竟,也许 value
是 "A" | "B"
类型,比如通过 pass 在编译器推断完整联合类型的表达式中:
builder(Math.random() <= 0.999 ? "A" : "B") // no error
此处您有 99.9% 的几率在 "A"
中通过,但您仍有可能在 "B"
中通过。编译器推断 T
是 "A" | "B"
。因此 next
参数的类型为 ComplexType<"A" | "B">
。所以当你调用这个时没有编译器错误:
builder(Math.random() <= 0.999 ? "A" : "B")({ value: "B" }); // no error
这意味着编译器在技术上是正确的 f(next)
可能是错误的。
GitHub 中存在多个问题,要求支持在通用函数体内缩小类型参数。与您的代码最相关的可能是 microsoft/TypeScript#27808。这需要一些方法来告诉编译器 T
应该是 either "A"
or "B"
并且不是 "A" | "B"
。也许语法会像 T extends_oneof [A, B]
或 (T extends A) | (T extends B)
或完全不同的东西。那么当您测试 value === "A"
时,编译器可能会得出 T extends A
的结论,并且一切正常。唉,目前还没有这样的支持。
现在你只需要解决它。如果您相当有信心没有人会错误地调用您的 builder()
,您可以使用 type assertion 并继续:
const builder = <T extends AB>(value: T) => (next: ComplexType<T>) => {
if (value === 'A') {
f(next as ComplexType<A>) // okay
}
}
如果您真的需要防止调用者使用 builder()
做错事,您可以制作越来越复杂的调用签名,相当于模拟“扩展其中之一”约束,例如:
type NoneOf<T, U extends any[]> =
[T] extends [U[number]] ? never : T;
type OneOf<T, U extends any[]> =
U extends [infer F, ...infer R] ? [T] extends [F] ? NoneOf<T, R> : OneOf<T, R> : never;
const builder = <T extends "A" | "B">(
value: T & OneOf<T, ["A", "B"]>
) => (next: ComplexType<T>) => {
if (value === 'A') {
f(next as ComplexType<"A">)
}
}
builder(Math.random() <= 0.999 ? "A" : "B"); // error now
builder2("A") // okay
builder2("B") // okay
但是编译器当然不能在 builder
的主体内遵循它(通用条件类型很难处理)所以你仍然需要类型断言。就我个人而言,我只会将您的原始签名与类型断言一起使用,如果您在实践中 运行 进入无效调用,则只会重新访问任何更复杂的内容。