首页 > 解决方案 > 为什么这个递归类型定义需要定义原语?

问题描述

如果我删除了这种类型的注释行,为什么不能编译?

type DeepPartial<T> =
    T extends Array<any> ? T
    : T extends ReadonlyArray<any> ? T
    : T extends Function ? T
    : T extends number ? T // why is this needed
    : T extends string ? T // same thing...
    : T extends boolean ? T // same thing...
    : T extends bigint ? T // same thing...
    : T extends symbol ? T // same thing...
    : T extends object ? { [P in keyof T]?: DeepPartial<T[P]> }
    : T;

function test<T>(o: DeepPartial<T>) {
    if (o === 1) return 0;
    if (o === '') return 1;
    if (o === false) return 2;
    if (o === 1n) return 3;
    if (o === Symbol.hasInstance) return 4;
    if (typeof o === 'number' && o === 2) return 5; //or this needed?
    return 6;
}

我认为 DeepPartial 类型上的注释行应该是不必要的,因为最终条件但编译器不同意(Playground Link)。

标签: typescripttypescript-generics

解决方案


具体的错误信息是

此条件将始终返回 'false',因为类型 'DeepPartial<T>' 和 '[number|string|boolean|bigint|symbol]' 没有重叠。

这与您if在将函数定义为时得到的所有错误消息非常相似,但最后一条语句除外function test<T>(o: T)

此条件将始终返回 'false',因为类型 'T' 和 '[number|string|boolean|bigint|symbol]' 没有重叠。

因为没有先区分类型,所以Ttype 在 的体内是不透明的类型test(),所以必须像对待unknown而不是对待any

引用类型的非同质混合并不典型T,所以如果这是您需要的,这可能是我建议的方法:

const enum Test {
  ONE,
  STRING,
  FALSE,
  BIG_ONE,
  HAS_INSTANCE,
  TWO,
  UNKNOWN
}

type TestMap<T> = T extends 1 ? Test.ONE
  : T extends '' ? Test.STRING
  : T extends false ? Test.FALSE
  : T extends 1n ? Test.BIG_ONE
  : T extends SymbolConstructor['hasInstance'] ? Test.HAS_INSTANCE
  : T extends 2 ? Test.TWO
  : Test.UNKNOWN;

function test<T>(o: T): TestMap<T> {
  function testImpl(o: any): Test {
    if (o === 1) return Test.ONE;
    if (o === '') return Test.STRING;
    if (o === false) return Test.FALSE;
    if (o === 1n) return Test.BIG_ONE;
    if (o === Symbol.hasInstance) return Test.HAS_INSTANCE;
    if (o === 2) return Test.TWO;
    return Test.UNKNOWN;
  }

  return testImpl(o) as TestMap<T>;
}

推荐阅读