typescript 如何Map到元组类型?

mrwjdhj3  于 2023-03-04  发布在  TypeScript
关注(0)|答案(2)|浏览(203)

是否有如下Map类型的方法:

type ObjectType = {
  prop1: number;
  prop2: string;
  prop3: boolean;
  prop4: string;
};

到以下内容?

type TupleType = [number, string, boolean, string]

我在第一次将ObjectType类型的对象转换为Object.values类型的数组时运气不佳,结果数组的类型将计算为(string | number | boolean)[],这不是元组类型,只是一个联合类型的数组。
从注解中我了解到,如果对象在最初定义后被修改,属性的顺序可能会改变,在这种情况下,我也可以使用Map
另一种可能的解决方案是使用如下所示的元组数组作为基础,以确保顺序不会改变:

type TupleListType = [
  [string, number],
  [string, string],
  [string, boolean],
  [string, string],
];
const argsTupleList: TupleListType = [
  ["prop1", 1],
  ["prop2", "two"],
  ["prop3", true],
  ["prop4", "four"],
]

如果我有办法将argsTupleList转换为[number, string, boolean, string]类型的数组,这个问题也会得到解决。
结果数组需要具有该类型,因此我可以将其传递给带有spread运算符的函数,如someFunc(...argsTuple)
如果我只是把对象转换成一个Object.values(args)的数组,我会得到:
spread参数必须具有元组类型或传递给rest参数。ts(2556)
重构函数以接受不同格式的args并不是我要找的。

xtfmy6hx

xtfmy6hx1#

基于共享的最小repro,我编写了一个函数,它只是以类型安全的方式转换结构。

type ObjectType = {
  prop1: number;
  prop2: string;
  prop3: boolean;
  prop4: string;
};

const args: ObjectType = {
  prop1: 1,
  prop2: "two",
  prop3: true,
  prop4: "four",
};

function someFunc(arg1: number, arg2: string, arg3: boolean, arg4: string) {
  console.log(arg1, arg2, arg3, arg4);
}

function composeParameters(obj: ObjectType): Parameters<typeof someFunc> {
  const { prop1, prop2, prop3, prop4 } = obj;
  return [prop1, prop2, prop3, prop4];
}

someFunc(...composeParameters(args));

Playground

i34xakig

i34xakig2#

为了避免试图让TypeScript编译器知道或关心对象类型中的属性顺序的问题(对象类型很多;keyof将产生一个union,它的顺序是由实现定义的,所以你可以从中得到一些东西,但是它可能与你期望的不同;有关更多信息,请参见How to transform union type to tuple type),我们将探索"另一种可能的解决方案",它使用元组的元组作为开始,而不是对象类型。
我们需要一个像argsTupleListToTupleType()这样的函数,它接受一个类型为键-值对数组的输入(每个键-值对的第一个元素都有一个类似键的类型),并将其转换为一个只包含值的数组。

declare function argsTupleListToTupleType<T extends any[]>(
  argsTupleList: readonly [...{ [I in keyof T]: readonly [PropertyKey, T[I]] }]
): T;

该函数是输出类型T中的generic,并且argsTupleList输入参数是T上的Map元组类型,其中T的每个元件已经被readonly对代替,该readonly对具有键状第一元件和作为第二元件的来自T的元件。(注意,readonly元组比非readonly元组接受 * 更多 * 输入,所以这更容易适应。)由于这是一个 * 同态 * Map类型(参见What does "homomorphic mapped type" mean?),编译器能够从Map类型的输入值 * 推断 * T
还要注意的是,我将argsTupleList的类型设置为readonly可变元组类型,因此readonly [...XXX]而不仅仅是XXX,这给了编译器一个提示,提示它应该尽可能将argsTupleList解释为元组类型,并且它应该接受任何constAssert的输入。本质上,这种 Package 只是使函数更适应以不同的方式被调用,而不真正改变基本操作。
这就是类型系统,那么实现呢?这是一个使用the map() array method的简单的argsTupleList.map(x => x[1])。TypeScript编译器不可能验证它是否满足调用签名,所以我们需要使用一个类型Assert来告诉它我们已经自己完成了检查,它不应该担心。(更多信息请参见Mapping tuple-typed value to different tuple-typed value without casts):

function argsTupleListToTupleType<T extends any[]>(
  argsTupleList: readonly [...{ [I in keyof T]: readonly [PropertyKey, T[I]] }]
): T {
  return argsTupleList.map(x => x[1]) as T;
}

让我们用你的例子来试试:

type TupleListType = [
  [string, number],
  [string, string],
  [string, boolean],
  [string, string],
];
const argsTupleList: TupleListType = [
  ["prop1", 1],
  ["prop2", "two"],
  ["prop3", true],
  ["prop4", "four"],
]

const tuple = argsTupleListToTupleType(argsTupleList);
// const tuple: [number, string, boolean, string]
console.log(tuple); // [1, "two", true, "four"]

看起来不错。tuple的类型和值都是我们所期望的。这也适用于不同的输入:

const t2 = argsTupleListToTupleType([
  ["a", 1], ["b", true], ["c", new Date()]
]);
// const t2: [number, boolean, Date]
console.log(t2); // [1, true, Date: "2023-02-28T16:47:56.073Z"] 

const inp = [["x", 0], ["y", null], ["z", undefined]] as const;
const t3 = argsTupleListToTupleType(inp); // const t3: [0, null, undefined]
console.log(t3); // [0, null, undefined]

Playground代码链接

相关问题