2

我想在一个函数中链接 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
    }
}
4

2 回答 2

2

目前没有办法通过检查像这样的值来缩小类型参数的范围。有可能这是真的,但这并不意味着是。毕竟,maybe是 type ,例如通过传入编译器推断完整联合类型的表达式:Tvaluevalue === "A"T"A"value"A" | "B"

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或者 "A" 不是 。也许语法会类似于或完全不同的东西。那么也许当你测试编译器会得出结论,一切都会工作。唉,目前还没有这样的支持。"B""A" | "B"T extends_oneof [A, B](T extends A) | (T extends B)value === "A"T extends A


现在,您只需要解决它。如果您相当有信心没有人会builder()错误地调用您,您可以使用类型断言并继续:

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(通用条件类型很难处理),所以你仍然需要类型断言。就我个人而言,我只是将您的原始签名与类型断言一起使用,并且仅在您在实践中遇到无效调用时重新访问任何更复杂的内容。

Playground 代码链接

于 2021-08-23T20:54:00.073 回答
0

你需要让你的函数f也知道你的泛型。

改变

const f = (next: ComplexType<A>) => {}

const f = <T extends AB>(next: ComplexType<T>) => {}

应该管用。

于 2021-08-23T18:27:00.810 回答