简体   繁体   中英

TypeScript conditional type and computed object property names

I'm having trouble using a conditional type in combination with a computed object property name. Basically I'm inserting rows into a database based on an input string. Then I'm typing the return object based on that input string. One of the properties in the return object is a computed name that is also based on the input string. So it seems like typescript would have all the info needed to verify that this is correct but it keeps giving me errors. Here's a very simplified example.


//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;

};

I made a workaround by doing an if-check on refColumn , then generating different objects depending on that. But using a computed property name would be waayyy easier. Any help would be appreciated.

You are running into multiple issues here:

(1) Computed property names are widened, one might say this is a bug :

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

So your example, [refColumn]: 123 will never behave as you want it to.

(2) Function bodies of functions with generic parameters are not validated iteratively with all possible subtypes (I guess the compiler might run forever then), instead they are validated with the type constraint. Thus if you have two generic types, whereas one is derived from the other, Typescript simply does not care. Usually this is not a problem because usually one type is directly the subtype of the other:

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

You've created a case where this is not the case, and constraint checking will always fail.

(3) One just cannot assign to a deferred conditional type. Typescript does not know which branch the conditional type will take (if it's evaluation is deferred), and as such only any can be assigned to it.

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

So with these three limitations it is basically impossible to write your function without manual typecasts. This is one of the few cases were an as any seems very reasonable.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM