我无法理解使用泛型参数访问映射类型时的类型脚本行为,这里是触发错误的最简单代码:

export type mockInstances = {
  app: {
    id: number;
    code: string;
  };
  setup: {
    id: number;
    code: string;
  };
};

type MockEntityName = keyof mockInstances;

type MockInstance<EntityName extends MockEntityName> =
  mockInstances[EntityName];

const testWrapper = <EntityName extends MockEntityName>() => {
  //Errors
  const a: Partial<MockInstance<EntityName>> = {
    id: 1,
  };
  const b: Partial<MockInstances[EntityName]> = {
    id: 2,
  };
  console.log('ab', a, b);

  //Error: Type '{ id: 1; }' is not assignable to type 'Partial<MockInstance<EntityName>>'.ts(2322)

  //Works
  const c: Partial<MockInstance<'app'>> = {
    id: 1,
  };
  const d: Partial<MockInstances['app']> = {
    id: 2,
  };

  const e: Partial<MockInstances[mockEntityName]> = {
    id: 2,
  };

  console.log('cde', c, d, e);
};

我期望TypeScrip编译器能够使用EntityName参数(作为索引或类型参数)将ab"赋值"给索引类型.

下面是这种奇怪的打字行为的简化版本:

export type mockInstances = {
  setup: {
    id: number;
    code: string;
    entityName: string;
    desc: string;
  };
};

type MockInstance<EntityName extends 'setup'> = mockInstances[EntityName];

const testWrapper = <EntityName extends 'setup'>(entityName: EntityName) => {
  //Error: Type '{ id: 1; code: "test"; entityName: EntityName; }'
  // is not assignable to type 'Partial<MockInstance<EntityName>>'.ts(2322)
  const a: Partial<MockInstance<EntityName>> = {
    id: 1,
    code: 'test',
    entityName,
  };

  const b: Partial<MockInstance<'setup'>> = {
    id: 1,
    code: 'test',
    entityName,
  };

  console.log(a, b);
};

所以我缩小了范围,在处理泛型时,Partial的行为并不像我期望的那样,我担心这可能会被认为是一个错误……

谢谢你对这个问题的任何启发.

推荐答案

问题是,Typescript 很少能够对generic种类型做出更高层次的结论.只有一些具体的方法是有效的,这些方法在microsoft/TypeScript#47109中有很大的描述.


您的MockInstances类型是equivalentRecord<MockEntityName, { id: number, code: string }>,但它们的表示方式不同.

您的版本只是一个手动写出的对象类型,其中Record是一个mapped type,已知属性值类型独立于键.编译器需要分析您的版本,并以某种方式识别其与映射类型的等价性.但它不会这样做.这样的结论要么需要人工推理(编译器还不能执行),要么需要有人编程编译器来判断这类事情……这意味着编译器将花费大量时间在许多无用的地方判断这类东西(例如,如果你修改了其中一个属性,但没有修改另一个).即使这样做是可能的,而且是一种普遍的需求,也可能不值得对性能造成冲击.

如果您希望编译器知道MockInstances‘S属性始终为{id: number, code: string},那么您应该以这种方式显式表示它:

type MockEntityName = "app" | "setup";
type MockInstances = Record<MockEntityName, { id: number, code: string }>;

接下来,您希望编译器能够理解,Partial<MockInstances[K]>>K无关,它将是{id?: number, code?: string}.但同样,这需要一种编译器不会执行的泛型分析.它必须对嵌套的映射类型type PartialMockInstances<K> = Partial<MockInstances[K]>进行抽象,才能看到K无关紧要.同样,它不能做到这一点.如果您希望编译器看到这种等价性,则需要将其写出为超过K的映射类型:

type PartialMockInstances =
    { [P in MockEntityName]: Partial<MockInstances[P]> };

如果您使用IntelliSense判断PartialMockInstances个,您将看到它等同于

type PartialMockInstances = {
    app: Partial<{
        id: number;
        code: string;
    }>;
    setup: Partial<{
        id: number;
        code: string;
    }>;
}

所以现在你可以写PartialMockInstances[K]而不是写Partial<MockInstances[K]>>,它涉及到indexing into一个映射类型,关于它(参见microsoft/TypeScript#47109),编译器can得出了有用的结论:

const testWrapper = <K extends MockEntityName>() => {
    const a: PartialMockInstances[K] = {
        id: 1,
    }; // okay
};

在这里,PartialMockInstances[K]是一个distributive object type(同样,请参见MS/TS#47109),编译器很乐意为它们赋值.

Playground link to code

Typescript相关问答推荐

带有联合参数的静态大小数组

如何在TypeScript对象迭代中根据键推断值类型?

有没有可能使用redux工具包的中间件同时监听状态的变化和操作

对深度对象键/路径进行适当的智能感知和类型判断,作为函数参数,而不会触发递归类型限制器

仅针对某些状态代码重试HTTP请求

TypeScrip:逐个 case Select 退出noUncheck kedIndexedAccess

有条件地删除区分的联合类型中的属性的可选属性

在包含泛型的类型脚本中引用递归类型A<;-&B

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

在打印脚本中使用泛型扩展抽象类

TYPE FOR ARRAY,其中每个泛型元素是单独的键类型对,然后在该数组上循环

如何使这种一对一关系在旧表上起作用?

TS2739将接口类型变量赋值给具体变量

NPM使用Vite Reaction应用程序运行预览有效,但我无法将其部署到Netlify

对有效枚举值的常量字符串进行常规判断

使用来自API调用的JSON数据的Angular

如何在Vue动态类的上下文中解释错误TS2345?

通过辅助函数获取嵌套属性时保留类型

使用受保护的路由和入门来响应路由

从函数参数推断函数返回类型