我正在try 为现有的API编写类型.下面是我try 编码的类型关系的一个最小示例.

const res1: res1Type = [
  {
    name: "FieldNameA",
    value: 123
  },
  {
    name: "FieldNameB",
    value: 123
  }
];
const res2: res2Type = {
  AlwaysPresent1: "test",
  AlwaysPresent2: 1,
  FieldNameA: {
    field1: 1,
    field2: 2
  },
  FieldNameB: {
    field1: 1,
    field2: 2
  }
};

type res1Type = {
  name: string;
  value: number;
}[];
type res2Type = {
  AlwaysPresent1: string;
  AlwaysPresent2: number;
  [k: string]: subType | string | number;
};
type subType = {
  field1: number;
  field2: number;
};

Res1是一个任意长度的数组,对象数组中每个条目的name字段都映射到res2中的一个字段(它有几个始终存在的额外字段).这些映射的字段具有对象属性.我已经设法使用索引签名来描述这些类型,但这需要我将其设置为UNION类型:

[k: string]: subType | string | number;

有没有办法更明确地将res1中的字段映射到res2中的字段?有没有索引签名的替代方案,允许我定义任意数量的字符串:子类型字段?

更新

实现了jcalz建议的方法,只做了一点小小的更改,显式地使res1Type成为Readonly.

type Res1Type = Readonly<{
    name: string;
    value: number;
}[]>;

推荐答案

要让Res2Type知道res1的元素的name个属性中的literal types个属性,唯一的方法是让它成为res1的类型中的generic或其他相关类型. 这里有一种写法:

type Res2Type<T extends Res1Type> = {
    AlwaysPresent1: string;
    AlwaysPresent2: number;
} & Record<T[number]["name"], SubType>

所以我们希望res2Res2Type<typeof res1>类型,使用TS typeof operator:


但我们需要小心.如果你只是annotate,res1,作为类型Res1Type,就像

const res1: Res1Type = [ ⋯ ];

然后,该变量的类型将是widenedRes1Type,并忽略有关初始化器表达式的任何特定内容.这将意味着res2Res2Type<Res1Type>类型看起来像{ AP1: string; AP2: string} & {[k: string]: SubType },intersection type很难使用(有关这种类型的讨论,请参见How to define Typescript type as a dictionary of strings but with one numeric "id" property),并且实际上根本不约束res2的键.

我们应该使用the satisfies operator而不是注释来对照Res1Type判断推断的初始化器类型,而不是扩大它:

const res1 = [ ⋯ ] satisfies Res1Type;

但我们still需要小心,因为推断出的name个属性的类型仍然是string.像{name: "xxx"}这样的对象被推断为类型{name: string}而不是{name: "xxx"}.如果我们想要后者,我们需要给编译器一个提示,我们关心文字类型.最简单的方法是在表达式上使用const assertion:

const res1 = [ ⋯ ] as const satisfies Res1Type;

that美元足以让它发挥作用.


那么,让我们看看我们得到了什么:

const res1 = [
    {
        name: "FieldNameA",
        value: 123
    },
    {
        name: "FieldNameB",
        value: 123
    }
] as const satisfies Res1Type;
/* const res1: [{
    readonly name: "FieldNameA";
    readonly value: 123;
}, {
    readonly name: "FieldNameB";
    readonly value: 123;
}] */

它编译时没有错误(在TS5.3+中),您可以看到它跟踪"FieldNameA""FieldNameB".所以我们想要res2的类型是

type R2Test = Res2Type<typeof res1>;
/* type R2Test = {
    AlwaysPresent1: string;
    AlwaysPresent2: number;
} & Record<"FieldNameA" | "FieldNameB", SubType> */

如果需要,可以使用该类型对res2进行注释:

const res2: Res2Type<typeof res1> = {
    AlwaysPresent1: "test",
    AlwaysPresent2: 1,
    FieldNameA: {
        field1: 1,
        field2: 2
    },
    FieldNameB: {
        field1: 1,
        field2: 2
    }
}; // okay

或者,您也可以根据以后的需要继续使用as const satisfies:

const res2 = {
    AlwaysPresent1: "test",
    AlwaysPresent2: 1,
    FieldNameA: {
        field1: 1,
        field2: 2
    },
    FieldNameB: {
        field1: 1,
        field2: 2
    }
} as const satisfies Res2Type<typeof res1>; // okay

Playground link to code

Res2Type

type Res1Type = {
    name: string;
    value: number;
}[];

const res1 = [
    {
        name: "FieldNameA",
        value: 123
    },
    {
        name: "FieldNameB",
        value: 123
    }
] as const satisfies Res1Type;


type SubType = {
    field1: number;
    field2: number;
};

Typescript相关问答推荐

具有映射返回类型的通用设置实用程序

使用FormArray在Angular中添加排序

即使子网是公共的,AWS CDK EventBridge ECS任务也无法分配公共IP地址

扩展函数签名中的参数类型,而不使用泛型

如何缩小变量访问的对象属性范围?

数组文字中对象的从键开始枚举

如何提取密钥及其对应的属性类型,以供在新类型中使用?

从子类构造函数推断父类泛型类型

在Typescript 中,有没有`index=unfined的速记?未定义:某个数组[索引]`?

已解决:如何使用值类型限制泛型键?

类型脚本-处理值或返回值的函数

tailwind css未被应用-react

将布尔值附加到每个对象特性

如何从接口中省略接口

为什么在这种情况下,打字脚本泛型无法正确自动完成?

类型';只读<;部分<;字符串|记录<;字符串、字符串|未定义&>';上不存在TS 2339错误属性.属性&q;x&q;不存在字符串

记录的子类型<;字符串,X>;没有索引签名

Karma Angular Unit测试如何创建未解析的promise并在单个测试中解决它,以判断当时的代码是否只在解决后运行

基于参数的 TS 返回类型

React router - 如何处理嵌套路由?