【问题标题】:Typescript: Required value in extending string literal union type打字稿:扩展字符串文字联合类型中的必需值
【发布时间】:2021-07-02 18:45:45
【问题描述】:

(目前使用 Typescript 4.3) 假设我定义了以下联合类型以及一些通用函数

types.ts

export type Foo = 'a' | 'b' | 'c'

export function Function<F extends Foo>() { ... }

我想做的就是以某种方式使 Function 的任何通用使用都应始终包含类型“a”(即,扩展 Foo 的任何 F 都必须包含“a”)

在代码中,我想要的是:

Function<'a' | 'b'>() // succeeds
Function<'c'>() // fails
Function<'a'>() // succeeds

有什么方法可以修改 Foo 或 Function 的类型定义来实现这一点?

【问题讨论】:

    标签: typescript


    【解决方案1】:

    一般来说,声明但未使用的类型参数是indicative of a problem。此外,已经有一个名为 Function 的全局对象,您可能不想对其进行遮蔽。为了更易于分析,我将您的示例修改为:

    type Foo = 'a' | 'b' | 'c';
    
    function func<F extends Foo>(f: F) { return f; } 
    // function func<F extends Foo>(f: F): F
    

    这里func() 采用F 类型的值,即constrainedFoo,并返回相同的值。因此类型签名是&lt;F extends Foo&gt;(f: F) =&gt; F

    调用func()时,可以手动指定类型参数F

    const x = func<"a" | "b">(Math.random() < 0.5 ? "a" : "b");
    // const x: "a" | "b"
    

    但这并不比让编译器推断更好:

    const y = func(Math.random() < 0.5 ? "a" : "b");
    // const y: "a" | "b"
    

    所以从现在开始,我将让编译器推断类型参数:


    您要求确保为F 指定的任何内容都包括"a"。换句话说,您希望F上限Foo下限"a"

    所以以下应该成功:

    func(Math.random() < 0.5 ? "a" : "b"); // okay
    func("a") // okay
    

    以下应该失败:

    func(Math.random() < 0.5 ? "a" : "d"); // fails
    func("c") // should fail but doesn't
    

    现在func("c") 失败


    不幸的是,TypeScript 中没有语法直接支持以这种方式“从下方”约束类型参数。 microsoft/TypeScript#14520 有一个长期存在的功能请求,以使用 super 语法 (as in Java) 支持此功能,因此人们可能会说类似 F super "a" 的内容,就像现在说 F extends Foo 的方式一样,但这样的功能不是直接支持。

    幸运的是,您可以使用conditional types 来模拟下限。例如:

    function func<F extends ("a" extends F ? Foo : "a")>(f: F) { return f; }
    // function func<F extends "a" extends F ? Foo : "a">(f: F): F
    

    这可能很难理解,但它强制执行F extends Foo"a" extends F(后者是F super "a" 的另一种说法)。它会按照您的意愿行事:

    func(Math.random() < 0.5 ? "a" : "b"); // okay
    func("a") // okay
    func("c") // fails
    

    不过,在您采用这种策略之前,可能值得考虑一下这是否有必要。如果您更改类型参数定义,您可能只需要一个上限就可以逃脱。

    与其说你想要一个类型参数F where "a" extends F and F extends Foo,不如说你想要一个类型参数G where G extends Foo,然后将F定义为@ 987654359@.

    特别是:

    function func<G extends Foo>(f: G | "a") { return f; }
    // function func<G extends Foo>(f: G | "a"): G | "a"
    

    现在"a" 不再可能不扩展F,因为F 只是我们给G"a" 的联合命名。当然,由于你改变了类型参数的定义,它会相应地改变推理和 IntelliSense 显示:

    func(Math.random() < 0.5 ? "a" : "b"); // okay
    // function func3<"b">(f: "a" | "b"): "a" | "b"
    
    func("a") // okay
    // function func<"a">(f: "a"): "a"
    
    func(Math.random() < 0.5 ? "a" : "d"); // fails
    
    func("c") // okay!
    // function func<"c"> (f: "a" | "c"): "a" | "c"
    

    最后一次调用没有失败,因为值"c" 的类型是"a" | "c"。这种类型的呼叫失败是否重要可能会影响您是否可以走这条路。但对于某些用例,这可能比显式下限仿真更可取。

    Playground link to code

    【讨论】:

      猜你喜欢
      • 2018-04-28
      • 2020-06-07
      • 2019-02-22
      • 2021-08-03
      • 2020-05-28
      • 1970-01-01
      • 1970-01-01
      • 2021-07-03
      相关资源
      最近更新 更多