【问题标题】:How can I define a tuple array with different tuple types explicitly in typescript?如何在打字稿中明确定义具有不同元组类型的元组数组?
【发布时间】:2020-03-26 12:55:39
【问题描述】:

我正在编写一个转换器,它使用查找对象将元组数组转换为对象,该对象告诉函数哪些字符串映射到哪些属性。但是,我找不到一种方法来告诉 typescript 它是一个具有特定类型的元组数组,生成的元组总是联合。下面是它的样子:

interface EndObj {
    a: number; 
    b: string;
    c?: number;
}
interface InitObj {
    d: string;
    e: string;
    f: string;
}

const map = {
    d: 'a',
    e: 'b',
    f: 'c'
} as const;

type MapType = typeof map;
type ResultTuple<T extends keyof InitObj> = [T, EndObj[MapType[T]]];
type ResultTupleArray = ResultTuple<keyof InitObj>[];

const resultObj: ResultTupleArray = [['d', 1], ['e', 3], ['f', 3]]; // invalid! the value of 'e' should only allow strings

我认为打字稿允许这样做的原因是,因为ResultTupleArray 是用keyof InitObj 定义的,所以生成的元组数组泛型总是相同的,所以T 总是相同的,而不是特定于每个数组条目,因此只能用联合来描述。

我是这样发现的:

const undetected: ResultTuple<keyof InitObj> = ['e', 4]; // should be invalid
const detected: ResultTuple<'e'> = ['e', 4]; // actually shows an error for 4 (Type 'number' is not assignable to type 'string'.)

对于一些上下文,转换器的外观如下:

function mapInitToEnd(resultO: ResultTupleArray) {
    const endObj: EndObj = {
        a: -1,
        b: ''
    };

    for (const tuple of resultO) {
        const [key, val] = tuple;
        const mappedKey = map[key];
        endObj[mappedKey] = val;
    }
    return endObj;
}

有没有办法告诉 typescript 泛型只对元组数组中的每个条目有效,而不是对整个数组有效?

【问题讨论】:

    标签: typescript tuples


    【解决方案1】:

    您希望分发您的 ResultTuple 定义,以便如果 T 是键的联合,则结果是元组的联合。给定您的代码,完成此操作的最简单方法是将定义定义为 distributive conditional type,这会免费为您提供此行为:

    type ResultTuple<T extends keyof InitObj> = T extends any ? [T, EndObj[MapType[T]]] : never;
    

    当您有 T extends U ? X : Y 其中T 是类型参数时,分布式条件类型就会启动。因此,为了在上面发生这种情况,我们添加了否则无用的T extends any ? ... : never 检查。现在你的代码会给你你期望的错误:

    const resultObj: ResultTupleArray = [['d', 1], ['e', 3], ['f', 3]];  // error!
    // number not assignable to string ----------> ~~~~~~~~
    

    还有其他方法可以得到这种行为;例如,通过构建 mapped type 和立即 looking up 其属性:

    type ResultTuple<T extends keyof InitObj> = { [K in T]: [K, EndObj[MapType[K]]] }[T];
    

    任何一种方式都应该有效。


    好的,希望对您有所帮助;祝你好运!

    Playground link to code

    【讨论】:

    • 非常详细的解释,我需要花一些时间来理解这一点。但是,虽然这适用于 resultObj 数组,但在尝试分配 endObj 属性时它不适用于转换器函数。这是什么原因?
    • “不起作用”具体是什么意思?我假设您在谈论 mapInitToEnd() 的实现中的一些编译器错误,但这将是一个单独的问题,与 TypeScript 缺乏对 correlated record types 的支持有关。
    • 是的,这正是我的意思。感谢您的洞察力!
    猜你喜欢
    • 1970-01-01
    • 2019-03-22
    • 1970-01-01
    • 2016-08-03
    • 1970-01-01
    • 2022-01-05
    • 2021-12-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多