typescript - 如何声明类型的 "transposed"版本?

标签 typescript typescript-generics mapped-types

我想定义一个 TS 函数来将对象转置为数组,例如:

const original  = { 
    value: [1, 2, 3], 
    label: ["one", "two", "three"] 
}

const transposed = [ 
    {value: 1, label: "one"}, 
    {value: 2, label: "two"},
    {value: 3, label: "three"}
]
然后我想声明一个接收 original 的函数和输出 transposed反之亦然,例如:
function tranpose(original: T): Array<T> // Incorrect, the output should be a transposed version of the object.
如何定义对象的转置版本并正确声明函数?
——
PS 我不是在问实现,只是在问打字和声明。

最佳答案

呵呵,你要Transpose<Transpose<T>>产生类似 T 的东西, 对?而且我假设,虽然在问题中没有明确说明,但您希望它适用于包含对象或数组的任意对象或数组,而不是专门“具有 valuelabel 属性的东西,其中包含数组”。
这在概念上很容易,但在处理对象与数组时事情会变得棘手。即使 mapped types should produce array/tuples from array/tuples ,有些陷阱编译器没有意识到它正在映射数组/元组,直到为时已晚,并且您的映射类型充满了可怕的数组方法,例如 "length" | "push" | "pop" |... .我假设你的实现也会有一些毛毛病,但我担心这里的类型,而不是实现。
这是我的版本,除了 IntelliSense 显示相同类型的联合(例如 type Foo = "a" | "a" | "a" 您希望看到 type Foo = "a" )的一些奇怪之外,它可以工作,幸运的是它不会影响类型的行为方式:

type Transpose<T> =
    T[Extract<keyof T, T extends readonly any[] ? number : unknown>] extends
    infer V ? { [K in keyof V]: { [L in keyof T]:
        K extends keyof T[L] ? T[L][K] : undefined } } : never;

declare function transpose<T>(t: T): Transpose<T>;
解释是我们正在遍历 T 的值/元素类型。找出输出类型的键应该是什么。那应该是 T[keyof T]但我们需要做 T[Extract<keyof T, ...]以确保数组不会把事情搞砸。然后我们大多只是转T[K][L]进入 T[L][K]在此过程中进行一些类型检查。

让我们测试一下。我们需要一个 const assertion或者类似的东西,如果你想让编译器跟踪哪些值在哪些键上:
const original = {
    value: [1, 2, 3],
    label: ["one", "two", "three"]
} as const
/* const original: {
    readonly value: readonly [1, 2, 3];
    readonly label: readonly ["one", "two", "three"];
} */
现在我们将转置它:
const transposed = transpose(original);
/* readonly [{
    readonly value: 1;
    readonly label: "one";
}, {
    readonly value: 2;
    readonly label: "two";
}, {
    readonly value: 3;
    readonly label: "three";
}]
*/


transposed.forEach(v => v.label) // transposed is seen as an array
transposed[1].label // known to be "two"
看起来挺好的。如果您在 transposed 上使用 IntelliSense你会看到它是三个相同类型的联合,但是🤷‍♂️。 (这是 TypeScript 的一个已知设计限制;参见 microsoft/TypeScript#16582 。可以强制编译器积极减少联合,如 here 所示,但这并不是这个问题的真正重点,所以我离题了。)输出类型被视为一个元组,因此它具有我假设您想要的所有数组方法。
然后我们应该能够通过转置转置的东西再次获得原始:
const reOriginal = transpose(transposed);
/* const reOriginal: {
    readonly value: readonly [1, 2, 3];
    readonly label: readonly ["one", "two", "three"];
} */

reOriginal.label.map(x => x + "!"); // reOriginal.label is seen as an array
再次,看起来不错。 reOriginal的类型是(模 IntelliSense)与 original 的类型相同.万岁!

Playground link to code
Playground link to code with IntelliSense fix

关于typescript - 如何声明类型的 "transposed"版本?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66302996/

相关文章:

typescript - 如何在 Typescript 中强制属性之间的相互依赖

typescript - 使用 Typescript 中的映射类型强制 bool 属性为 true 或 false

Angular - 日期管

javascript - 转换 RxJS 5 Rx.Observable.timer(3000).mapTo({ id : 1 }) to RxJS 6?

javascript - typescript 中的错误消息说函数参数与@TypeScript 0.9.1 不匹配

css - 基于另一个创建新的字符串文字类型

typescript - Typescript 中的多种类型谓词

javascript - 更新具有匹配属性的对象并忽略新属性

typescript : generic type with primitive types constrain

typescript - 在 TypeScript 中动态提取类型