对于下面的代码,我使用了用Ryan Dabler's Medium post编写的算术运算类型.

以下代码片断会产生一个编译器错误

// Test interface using the Add<A, B> type
interface Foo<Sum extends number> {
  increment(): Add<Sum, 1>;
  incrementString(): `${Add<Sum, 1>}`;
  /*                    ~~~~~~~~~~~
  Type 'Length<[...BuildTuple<Sum, []>, any]>' is not assignable to type 'string | number | bigint | boolean | null | undefined'.
  Type 'unknown' is not assignable to type 'string | number | bigint | boolean | null | undefined'.(2322) */
  sumParam(param: `${Add<Sum, 1>}`): void;
  /*                 ~~~~~~~~~~~ Same error */
}

不能将类型‘Long&lt;[...BuildTuple&lt;Sum,[]&gt;,any]&gt;’赋给类型‘字符串|数字|Bigint|布尔|空|未定义’. 类型‘UNKNOWN’不可赋值给类型‘STRING|NUMBER|BIGINT|Boolean|NULL|UNDEFINED’.(2322)

为什么我会收到这个错误?据我所知,当在interface(或type,就此而言)内的函数中,编译器可以解析Add<Sum, 1>,但不能解析`${Add<Sum, 1>}`.

下面是一些具有意外结果的其他测试,上面的代码就是上述代码.

// Testing types
type One = Add<0, 1> // 1
type OneString = `${Add<0, 1>}` // "1" - The compiler doesn't report an error. Why does it work now but not before?

// Testing with object
// I can declare all the functions even though there was an error before
const bar: Foo<0> = {
  increment: () => 1,
  incrementString: () => '1',
  sumParam: (param: '1') => {},
}

// Testing the functions
// They all run with the expected result even though there was an error before
const baz = bar.increment() // 1 
const qux = bar.incrementString() // "1"
const quux = bar.sumParam('1'); // void

Here是TS操场上的完整代码.我在那里和我的机器上测试了它.

推荐答案

打字脚本类型判断器不具有人类智能的优势,也不具备查看实用程序类型的名称作为它们正在做什么的提示的能力.vt.给出

type Add<A extends number, B extends number> =
    Length<[...BuildTuple<A>, ...BuildTuple<B>]>;

比方说,当你写Add<2, 3>的时候,编译器就会继续并计算它,生成数字literal type 5.但是,如果您编写Add<Sum, 1>,其中Sum是从constrainednumbergeneric类型参数,则编译器不知道如何处理它.它不能直接判断它,因为它不知道Sum是什么.因此,它没有对其进行判断,基本上是不透明的.

它也不能看着Add<Sum, 1>然后说,"好吧,给出Add的定义,BuildTuple的定义,Length的定义,我知道无论Add<Sum, 1>是什么,它都可以赋给number."这将需要更高阶的推理能力,可能会得到数字提示名称AddLength的帮助.它只是不知道.因此,如果您将Add<Sum, 1>放在扩展number(或string | number | bigint | boolean | null | undefined)所需的类型位置,编译器将会报错,因为它不能确定它是否工作.

在这种情况下,最简单的处理方法是用X & Y(intersection)或Extract<X, Y>(Extract utility type)之类的东西替换X,因为您知道类型X可以赋给类型Y,但编译器不能.编译器知道X & Y可以赋值给XY,如果您知道X可以赋值给Y,那么X & Y等同于X.具体地说:

interface Foo<Sum extends number> {
    increment(): Add<Sum, 1>;
    incrementString(): `${number & Add<Sum, 1>}`; // okay
    sumParam(param: `${number & Add<Sum, 1>}`): void; // okay
}

这修复了错误,因为它回避了问题.现在编译器不必知道Add<Sum, 1>number,因为无论Add<Sum, 1>是什么,类型number & Add<Sum, 1>肯定是number.只要我们的假设是正确的,Add<Sum, 1>将是某种类型的number,额外的number &就不会对最终判断的类型产生影响.

Playground link to code

Typescript相关问答推荐

TypScript如何在 struct 上定义基元类型?

在TypeScript中使用泛型的灵活返回值类型

状态更新后未触发特定元素的Reaction CSS转换

Vue SFC中的多个脚本块共享导入的符号

如何以Angular 形式显示验证错误消息

类型,其中一条记录为字符串,其他记录为指定的

嵌套对象的类型

使用订阅时更新AG网格中的行

如何在NX工作区项目.json中设置类似angular.json的两个项目构建配置?

在另一个类型的函数中,编译器不会推断模板文字类型

在类型脚本中创建泛型类型以动态追加属性后缀

如何将定义模型(在Vue 3.4中)用于输入以外的其他用途

正确使用相交类型的打字集

类型脚本-在调用期间解析函数参数类型

声明遵循映射类型的接口

对于VUE3,错误XX不存在于从不存在的类型上意味着什么?

在REACT查询中获取未定义的isLoading态

仅当类型为联合类型时映射属性类型

如何使用 AWS CDK 扩展默认 ALB 控制器策略?

为什么我无法在 TypeScript 中重新分配函数?