TypeScript 条件类型和计算对象 属性 名称

TypeScript conditional type and computed object property names

我在结合使用条件类型和计算对象 属性 名称时遇到问题。基本上我是根据输入字符串将行插入数据库。然后,我根据该输入字符串键入 return 对象。 return 对象中的一个属性是一个计算名称,它也基于输入字符串。因此,打字稿似乎拥有验证这是否正确所需的所有信息,但它一直给我错误。这是一个非常简单的例子。


//types of the table rows from the database
interface FirstTableRow {
    id: number,
    someFirstRefId: number
};
interface SecondTableRow {
    id: number,
    someSecondRefId: number
};

//maps which table we're working with to its reference column name
const whichToExtraColumn = {
    first: 'someFirstRefId',
    second: 'someSecondRefId'
} as const;

//maps the table to the returned row type
type ConstToObj<T> = (T extends 'first'
    ? FirstTableRow
    : T extends 'second'
    ? SecondTableRow
    : never
);

function createFirstOrSecond<
    T extends keyof typeof whichToExtraColumn
>(
    which: T
): ConstToObj<T> {

    //gets the reference column name for this table
    const refColumn = whichToExtraColumn[which];

    //do database stuff....
    const insertId = 1;

    //build the inserted row
    const test: ConstToObj<T> = {
        id: insertId,
        [refColumn]: 123
    };
    // ^ Type '{ [x: string]: number; id: number; }' is not assignable to type 'ConstToObj<T>'

    return test;

};

我通过对 refColumn 进行 if-check 来解决这个问题,然后根据它生成不同的对象。但是使用计算的 属性 名称会更容易。任何帮助将不胜感激。

您运行正在处理多个问题:

(1) 计算的 属性 名称被加宽,有人可能会说 this is a bug:

type Key = "a" | "b";
let a: Key = Math.random() ? "a" : "b";
const result = { [a]: 1 };
//    -> { [x: string]: number }

所以您的示例 [refColumn]: 123 永远不会像您希望的那样运行。

(2) 具有通用参数的函数的函数体不会使用所有可能的子类型进行迭代验证(我猜编译器可能会 运行 永远如此),而是使用类型约束对其进行验证。因此,如果你有两个泛型类型,而一个是从另一个派生的,Typescript 根本不关心。通常这不是问题,因为通常一种类型直接是另一种类型的子类型:

function assign<A extends B, B extends 1 | 2 | 3>(a: A) {
    const b: B = a;
}

您创建了一个情况,但情况并非如此,约束检查将始终失败。

(3) 不能分配给延迟条件类型。 Typescript 不知道条件类型将采用哪个分支(如果它的评估被推迟),因此只能将 any 分配给它。

function plusOne<A extends 1 | 2>(a: A) {
    const b: (A extends 1 ? 2 : 3) = a + 1;
}

因此,由于这三个限制,基本上不可能在没有手动类型转换的情况下编写您的函数。这是为数不多的 as any 似乎非常合理的案例之一。