【问题标题】:Is there a way to narrow typescript overloads with optional parameters?有没有办法用可选参数缩小打字稿重载?
【发布时间】:2021-03-11 12:51:01
【问题描述】:

我正在尝试执行以下操作:

interface RgbColor {
  r: number;
  g: number;
  b: number;
}

function rgbToHex(r: RgbColor, g?: undefined, b?: undefined): string
function rgbToHex(r: number, g: number, b: number): string
function rgbToHex(r: number|RgbColor, g?: number, b?: number): string {
  if (r instanceof Object) {
    // must be RgbColor
    g = r.g;
    b = r.b;
    r = r.r;
  }

  // A) r was an RgbColor and we've already set r, g, and b to numbers in the if block
  // B) we're meeting the second overload and r, g, and b are all numbers
  // either way we know they are all numbers now

  let rHex = r.toString(16);
  let gHex = g.toString(16); // ERROR: Object is possibly 'undefined'.
  let bHex = b.toString(16); // ERROR: Object is possibly 'undefined'.

  if (rHex.length == 1) rHex = "0" + rHex;
  if (gHex.length == 1) gHex = "0" + gHex;
  if (bHex.length == 1) bHex = "0" + bHex;

  return "#" + rHex + gHex + bHex;
}

我在ERROR cmets 指示的行上遇到错误。

从javascript的角度来看,这个功能很好:

function rgbToHex(r , g, b) {
  if (r instanceof Object) {
    g = r.g; 
    b = r.b; 
    r = r.r;
  }

  let rHex = r.toString(16);
  let gHex = g.toString(16);
  let bHex = b.toString(16);

  if (rHex.length == 1) rHex = "0" + rHex;
  if (gHex.length == 1) gHex = "0" + gHex;
  if (bHex.length == 1) bHex = "0" + bHex;

  return "#" + rHex + gHex + bHex;
}

rgbToHex({ r: 120, g: 50, b: 5 }) // "#783205"
rgbToHex(120, 50, 5) // "#783205"

我的问题是如何设置函数签名,这样它就可以正常工作,而无需进行任何转换,也无需在函数体中设置任何冗余变量。即我想避免做let gVal: number = g || (r as any).g;

另一种选择是将其拆分为两个函数并让一个调用另一个;但我不会用 javascript 来做,所以我必须用 typescript 来做这件事似乎是错误的。

TS Playground Link

对不起,如果这是重复的。我花了几个小时查看类似的问题,但找不到任何解决此问题的方法。

【问题讨论】:

    标签: typescript overloading narrowing


    【解决方案1】:

    问题是,变量 gb 将始终是 number | undefined 类型,如果不声明新变量,就无法永久重新分配它们。

    您有 4 个选项:

    1. 使用非空断言运算符!,这意味着你需要写g!.toString(16),而不是写g.toString(16),强制它是number类型。您也可以使用类型转换来解决它。
    2. 您可以将其拆分为多个函数,提取 rgb 变量,然后返回原始的rgbToHex 函数。
    3. 正如@Oblosys 的回答中提到的,您可以使用联合类型。
    4. 您可以在函数顶部创建一个新对象,并在其中填充一个新的RgbColor 对象。

    【讨论】:

    • “问题是,变量 g 和 b 将始终是 number | undefined 类型,它们不能被永久重新分配,除非声明一个新变量。”这就是为什么问题是关于重载缩小的原因,如果 TS 能够在 if 块中找出 r 的形状为 RgbColor,那么其余的逻辑应该遵循 if 后面的几行 cmets堵塞。我不知道非空断言运算符,对此表示赞同。我希望还有一些我不知道的东西可以回答指定的问题。
    【解决方案2】:

    您可以通过为参数引入数组类型来实现完全类型化:

    type Args = [rgb: RgbColor] | [r: number, g: number, b: number]
    

    然后将其用作discriminating union,其中数组长度是 TypeScript 将用于确定我们正在处理的联合的哪些备选方案的区分字段:

    function rgbToHex(...args: Args): string {
      const [r,g,b] = args.length === 1
      ? [args[0].r, args[0].g, args[0].b] // args: [rgb: RgbColor]
      : args                              // args: [r: number, g: number, b: number]
    
      // From here on, `r`, `g`, & `b` all have type `number`
      ..
    }
    

    TypeScript playground

    【讨论】:

    • 这并不可怕,但这意味着我正在重写函数体以解决打字稿的不足。就目前而言,函数体是正确的。另外我不确定是否有明确的方法来编写解析逻辑;当您第一次阅读它时,总是需要一分钟才能弄清楚发生了什么,而且在这种代码中引入错误太容易了。 (即[args[0].r, args[0].b, args[0].g] 在这种情况下显然不是错误的)
    • 我仍在等待一个实际的答案,但我喜欢这个想法作为备份。我能想到的最简洁的逻辑表述是this
    • 我相当肯定控制流分析不会缩小函数参数,因为重新分配它们不是常见的做法。我也会选择constletif 上的三元组,但我的解决方案中有一个不必要的数组,这使得它更难阅读。
    • 在 ES6 之前,重新分配参数是标准做法,W3 still has an old recommendation to do just that for default parameter initialization。有趣的是,TS allows for this pattern of usage,所以这确实是一个过载缩小问题。
    【解决方案3】:

    在谷歌搜索、阅读其他 SO 问题并浏览 typescript github 问题后,看起来这是 typescript 中尚未实现的功能。

    在函数外起作用的重载收缩还没有在函数内应用:

    // function definition for swapping between number and string types
    function foo(bar: string): number // overload 1
    function foo(bar: number): string // overload 2
    function foo(bar: number | string): number|string 
    { ... }
    
    const a = foo('string');
    a.toPrecision(); // TS knows a is a number, so this has no errors
    
    const b = foo(5);
    b.toLowerCase(); // TS knows b is a string, so this has no errors
    
    const c = Math.random() < .5 ? a : b; // TS knows c as string | number
    
    // the actual implementation of an overloaded method does not count as 
    // one of the possible overloads, so calling foo with a variable of type (string|number)
    // is not valid
    const d = foo(c); // error on parameter 'c': No overload matches this call.
    

    所以这一切都符合预期。但是当我们尝试实现foo 时,我们会看到在方法中没有应用相同的排除逻辑:

    function foo(bar: string): number // overload 1
    function foo(bar: number): string // overload 2
    function foo(bar: number | string): number | string {
      return bar; // this is valid, it clearly should not be
    }
    

    看起来打字稿团队尚未在函数内进行代码分析,以了解该函数的重载,似乎严格执行了实现的签名。这显然是错误的。

    QED:工具错误。等待修复。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-04-30
      • 2019-11-06
      • 2019-09-15
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多