gpt4 book ai didi

typescript - 在类型中使用元组而不是联合数组

转载 作者:行者123 更新时间:2023-12-02 00:13:11 25 4
gpt4 key购买 nike

有没有办法更严格地键入以下两个函数 toCsv()toArray() 使得 typeof csv

[["key", "life", "goodbye"], ...[string, number, boolean][]]

代替

[("key" | "life" | "goodbye")[], ...(string | number | boolean)[][]]

typeof originaltypeof values相同,即

{ key: string, life: number, goodbye: boolean }[]

代替

{ key: any, life: any, goodbye: any }[]

我意识到 { key: 'value', life: 42, goodbye: false } 使用 for...in 的迭代顺序不能保证,我'我很好。任何将键与每一行的相应值对齐的一致顺序都是可以接受的,即使 TypeScript 编译器不会生成与运行时相同的顺序,因为使用不依赖于任何特定的顺序。

type Key<T> = Extract<keyof T, string>;
type Column<T> = [Key<T>, ...T[Key<T>][]];
type Columns<T> = [Key<T>[], ...T[Key<T>][][]];

function toCsv<T> (array: T[]): Columns<T> {
const columns: Column<T>[] = [];

for (const key in array[0]) {
columns.push([key, ...array.map(value => value[key])]);
}

const keys: Key<T>[] = [];
const rows: T[Key<T>][][] = array.map(() => []);

for (const [key, ...values] of columns) {
keys.push(key);

for (const [index, row] of rows.entries()) {
row.push(values[index]);
}
}

return [keys, ...rows];
}

function toArray<T> (csv: Columns<T>): T[] {
const [keys, ...rows] = csv;

return rows.map(
row => keys.reduce(
(o, key, index) => Object.assign(o, { [key]: row[index] }),
{} as Partial<T>
) as T
);
}

const values = [{ key: 'value', life: 42, goodbye: false }];
const csv = toCsv(values);
const original = toArray(csv);

最佳答案

我不会尝试采用输出特定元组顺序的方式。正如您已经指出的那样,实际结果可能不是按该顺序排列的,因此将其呈现为此类类型会产生误导。有时对编译器说谎是必要的或有用的,但在这种情况下,我看不到主要的好处。

此外,即使我想这样做,要让编译器变成像keyof T这样的联合实际上并不容易。成一个有序的元组。类型"a"|"b""b"|"a" 的类型完全相同;编译器很可能会在不让您知道的情况下使用一个或另一个或两者,因此您所做的任何事情都会产生 ["a", "b"]对比["b", "a"]当您不期望它时,它可能会发生变化。你可以abuse the type system实现这一目标,但它确实很困惑且脆弱,我建议不要这样做。


如果你真的想使用元组,你可以通过像 "a"|"b" 这样的联合来避免排序问题。变成所有可能的元组的联合,如 ["a", "b"] | ["b", "a"] .这实际上在类型系统中更容易表示,因为它在联合成员上是对称的,但仍然很困惑,因为一旦您拥有相当数量的属性,联合中的元素数量就会变得难以管理(是的阶乘)。这里的好处是您对输出类型尽可能诚实。这是实现它的一种方法:

type UnionToAllPossibleTuples<T, U = T> = [T] extends [never]
? []
: T extends unknown ? [T, ...UnionToAllPossibleTuples<Exclude<U, T>>] : never;

type MergedColumns<T> = UnionToAllPossibleTuples<
{ [K in keyof T]: { key: K; val: T[K] } }[keyof T]
>;

type Lookup<T, K> = K extends keyof T ? T[K] : never;

type UnmergeColumns<T> = T extends any
? [
{ [K in keyof T]: Lookup<T[K], "key"> },
...{ [K in keyof T]: Lookup<T[K], "val"> }[]
]
: never;

type Columns<T> = UnmergeColumns<MergedColumns<T>>;

你可以验证它是否有效:

interface TestType {
key: string;
life: number;
goodbye: boolean;
}

type ColumnsTestType = Columns<TestType>;
// type ColumnsTestType =
// | [["key", "life", "goodbye"], ...[string, number, boolean][]]
// | [["key", "goodbye", "life"], ...[string, boolean, number][]]
// | [["life", "key", "goodbye"], ...[number, string, boolean][]]
// | [["life", "goodbye", "key"], ...[number, boolean, string][]]
// | [["goodbye", "key", "life"], ...[boolean, string, number][]]
// | [["goodbye", "life", "key"], ...[boolean, number, string][]]

这很有趣,但可能仍然太脆弱和凌乱,不值得我推荐。


备份,您真正关心的似乎是保留类型 TtoCsv()toArray() ,并且原始数组类型虽然准确,但有损。在那种情况下,对您的原始代码做这个小改动怎么样?

type Columns<T> = [Key<T>[], ...T[Key<T>][][]] & { __original?: T };

在这里,Columns<T>本质上与以前的类型相同,但有一个名为 original 的可选额外属性类型为 T .该属性在运行时永远不会实际存在或使用。是的,您可能在这里欺骗了编译器,但实际上并没有撒谎;从 toCsv() 出来的东西将没有 __original属性,与 {__original?: T} 匹配.不过这种欺骗很有用,因为它为编译器提供了足够的信息来了解往返过程中发生的情况。观察:

const values = [{ key: "value", life: 42, goodbye: false }];
const csv = toCsv(values);
// const csv: Columns<{ key: string; life: number; goodbye: boolean; }>
const original = toArray(csv);
// const original: { key: string; life: number; goodbye: boolean; }[]

这对我来说很好,我会推荐。


回顾:如果你想欺骗编译器,就不要在元组顺序上撒谎。说实话元组顺序太乱了。相反,在可选属性上撒一个小谎。

好的,希望对你有帮助。祝你好运!

Link to code

关于typescript - 在类型中使用元组而不是联合数组,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58194653/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com