打字稿:从条件类型派生类型

typescript: Derive types from Conditional Types

打字稿支持 Conditional Types。 但是当我尝试将 op 的值设置为字符串时,它给我错误,我如何检查类型并分配值?

export const t = <IsMulti extends boolean = false>(): void => {
    const value = 'test';
    type S = string;
    type D = IsMulti extends true ? S[] : S;
    const op: D = value;
    console.log(op);
};

错误:Type 'string' is not assignable to type 'D'.

我什至尝试添加相同类型的参数 IsMulti 并添加基于它的检查

export const t = <IsMulti extends boolean = false>(isMulti: IsMulti): void => {
    const value = 'test';
    type S = string;
    type D = IsMulti extends true ? S[] : S;
    if (!isMulti) {
        const op: D = value;
        console.log(op);
    }
};

它仍然给出相同的错误。

conditional types which depend on as-yet unspecified generic 类型参数的计算通常被编译器延迟;如果编译器不知道 IsMulti 是什么,那么它也不知道 IsMulti extends true ? S[] : S 是什么,所以它不会让你分配一个类型的值(比如)S 到该条件类型,因为它无法验证这是一个 type-safe 操作。编译器甚至不会真正尝试评估此类条件类型;它使它们成为不透明的东西,几乎没有任何东西可以分配给它们。

对于您的第一个示例,这是编译器所期望的行为;将 string 分配给 op 确实不安全,因为 D 很可能是 string[]。没有什么能阻止某人打电话给 t<true>()。仅仅因为 IsMulti 有一个 falsedefault 并不意味着它 false.


对于您的第二个示例,这是目前 TypeScript 的限制。编译器无法使用 control flow analysis to narrow type parameters. And therefore it cannot verify assignability to a See microsoft/TypeScript#33912 获取详细信息。

当您检查 if (!isMulti) 时,编译器将后续代码块中 isMulti 的类型从 IsMulti 类型缩小为 false:

export const t = <IsMulti extends boolean = false>(isMulti: IsMulti): void => {
  if (!isMulti) {
    // isMulti is now known to be false 
    const fls: false = isMulti; // okay
    const tru: true = isMulti; // error
  }
};

但它不会缩小类型参数IsMulti本身。一般来说,这也是编译器所希望的行为,因为 IsMulti extends boolean 意味着 IsMulti 实际上可能是完整的 union boolean (as opposed to just true or just false) and so checking a value of type IsMulti would not be useful to narrow IsMulti itself. All you could say with if (!isMulti) is that IsMulti could not be just true... but it might still be boolean. Of course if it is boolean, then D, a distributive conditional type 将成为联合 S[] | S,你应该被允许给它分配一个 string 。所以没有合理的方法可以将 "test" 分配给 op 应该是不安全的。

然而,编译器无法验证这一点。它顽固地让 IsMulti 单独存在,并且不会让你分配任何东西给 D 因为它推迟了计算。如果有一些受支持的方法来缩小类型参数,或者验证对通用条件类型的可分配性,那就太好了。目前,确实没有。上面链接的 GitHub 问题是一个改进此问题的功能请求,并且有很多相关的请求可能会有所帮助(例如,microsoft/TypeScript#27808 可以将 IsMulti 限制为 true 或恰好 false,然后大概 if (!isMulti) 会将先前的泛型类型 IsMulti 缩小为特定类型 false,并且 D 将被急切地求值)。您可以解决这些问题并给他们一个 ,但这里不会很快发生任何事情。


相反,在这种情况下,您比编译器更了解类型,您可以使用 type assertion 来抑制可分配性错误:

export const t = <IsMulti extends boolean = false>(isMulti: IsMulti): void => {
  const value = 'test';
  type S = string;
  type D = IsMulti extends true ? S[] : S;
  if (!isMulti) {
    const op = value as D; // okay
    console.log(op);
  }
};

通过编写 value as D,您告诉编译器您知道 value 绝对是 D 类型,即使编译器不能。这允许程序编译而不会出错。请注意,这不是魔术,通过进行类型断言,您可以将类型安全的部分责任从编译器上移开。如果你犯了一个错误或者对编译器撒了谎,它总不能抓住它:

if (!isMulti) {
  const op = ["oopsie"] as D; // still okay
  console.log(op);
}

所以要小心!

Playground link to code