我正在try 编写一个函数fc,该函数接受2个对象并以完全类型安全的方式返回它们的合并版本:

const rfc = fc({ num: [1, 2, 3] }, { num: [4, 5, 6] });
//   ^? { num: [1, 2, 3, 4, 5, 6] }

怎么做呢?

  • 平坦

    让我们try 简单地使用可变元组的TypeScript spread语法:

    type t1a = [1, 2, 3]
    type t2a = [4, 5, 6]
    
    type ra = [...t1a, ...t2a]
    //   ^? [1, 2, 3, 4, 5, 6]
    // awesome, works
    

    让我们使用通用的类型脚本函数(在this blog的帮助下)来try 一下:

    function fa<T1 extends number[], T2 extends number[]>(t1: [...T1], t2: [...T2]): [...T1, ...T2] {
      return [...t1, ...t2];
    }
    const rfa = fa([1, 2, 3], [4, 5, 6]);
    //   ^? [1, 2, 3, 4, 5, 6]
    // awesome, works
    
  • 嵌套的

    让我们try 在嵌套props 上传播类型:

    type t1b = { num: [1, 2, 3] }
    type t2b = { num: [4, 5, 6] }
    
    type rtb = [...t1b['num'], ...t2b['num']]
    //   ^? [1, 2, 3, 4, 5, 6]
    // awesome, works
    

    现在使用泛型:

    function fc<T1 extends { num: number[] }, T2 extends { num: number[] }>(
      t1: T1,
      t2: T2,
    ): { num: [...T1['num'], ...T2['num']] } {
      return { num: [...t1.num, ...t2.num] as any };
    }
    const rfc = fc({ num: [1, 2, 3] }, { num: [4, 5, 6] });
    //   ^?  {num: number[]}
    // why?? why not [1, 2, 3, 4, 5, 6] ??
    

    为什么rfc不是我预想的[1, 2, 3, 4, 5, 6]

推荐答案

在某些情况下,类型脚本只能推断tuple typesliteral types,而generic类型参数T1T2 constrained{ num: number[] }不在其中.(您可以查看microsoft/TypeScript#T276以查看对推断文本类型的一些情况的描述.)编译器将T1 extends { num: number[] }解释为类似于"T1将至少具有类型number[]num属性,并且它还可能具有更多属性"之类的意思,并且它并不打算为num推断出比number[]更具体的任何东西.

如果希望编译器为文字值推断更具体的类型,则需要给它一个提示来执行此操作.fc()函数中的caller可以使用const assertions来执行此操作,例如

const rfc = fc({ num: [1, 2, 3] } as const, { num: [4, 5, 6] } as const);
// T1 = readonly { num: [1, 2, 3] }
// T2 = readonly { num: [4, 5, 6] }
// const rfc: { num: [1, 2, 3, 4, 5, 6]; }

但如果呼叫者忘记这样做,就不会发生这种情况.


幸运的是,类型脚本5.0引入了the const modifer for generic type parameters,这样您就可以将T1T2标记为类型参数,它们的类型参数大多应该被视为调用者使用的as if.就像这样:

function fc<
  const T1 extends { num: number[] },
  const T2 extends { num: number[] }
>(t1: T1, t2: T2): { num: [...T1['num'], ...T2['num']] } {
  return { num: [...t1.num, ...t2.num] as any };
}

现在,当你调用fc()时,你会得到以下行为:

const rfc = fc({ num: [1, 2, 3] }, { num: [4, 5, 6] });
// T1 = readonly { num: [1, 2, 3] }
// T2 = readonly { num: [4, 5, 6] }
// const rfc: { num: [1, 2, 3, 4, 5, 6]; }

因此,即使调用方没有在任何地方使用as const,T1T2也被推断为与以前相同的类型.

Playground link to code

Typescript相关问答推荐

如何在类型脚本中声明对象其键名称不同但类型相同?

如果我有对象的Typescript类型,如何使用其值的类型?

在Angular中,如何在文件上传后清除文件上传文本框

忽略和K的定义

如何正确地对类型脚本泛型进行限制

编剧错误:正在等待Expect(Locator).toBeVisible()

CDK从可选的Lambda角色属性承担角色/复合主体中没有非空断言

在HighChats列时间序列图中,十字准线未按预期工作

如何通过TypeScrip中的函数防止映射类型中未能通过复杂推理的any值

使用条件类型的类型保护

类型';字符串|数字';不可分配给类型';未定义';.类型';字符串';不可分配给类型';未定义';

必需的输入()参数仍然发出&没有初始值设定项&q;错误

可赋值给类型的类型,从不赋值

类型脚本中参数为`T`和`t|unfined`的重载函数

如何为带有参数的函数类型指定类型保护?

我可以使用JsDocs来澄清Typescript实用程序类型吗?

从联合提取可调用密钥时,未知类型没有调用签名

元素隐式具有any类型,因为string类型的表达式不能用于索引类型{Categories: Element;管理员:元素; }'

TS 条件类型无法识别条件参数

如何使用 TypeScript 接口中第一个属性的值定义第二个属性