Условный тип TypeScript и имена свойств вычисляемого объекта

У меня возникли проблемы с использованием условного типа в сочетании с именем свойства вычисляемого объекта. В основном я вставляю строки в базу данных на основе входной строки. Затем я набираю возвращаемый объект на основе этой входной строки. Одно из свойств возвращаемого объекта - это вычисленное имя, которое также основано на входной строке. Похоже, что в машинописном тексте есть вся информация, необходимая для проверки, что это правильно, но он продолжает выдавать мне ошибки. Вот очень упрощенный пример.


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

};

Я нашел обходной путь, выполнив if-check для refColumn, а затем сгенерировав разные объекты в зависимости от этого. Но было бы проще использовать вычисленное имя свойства. Любая помощь будет оценена по достоинству.


person zbauman    schedule 12.04.2021    source источник
comment
Я думаю, что проблема, с которой работает компилятор Typescript, заключается в том, что "first" | "second" является допустимым подтипом keyof typeof whichToExtraColumn, поэтому ConstToObj<T> может быть FirstTableRow | SecondTableRow, и объект не может быть ему назначен. К сожалению, сообщение об ошибке очень короткое, поэтому трудно сказать, почему именно это несоответствие (приведенное выше является предположением)   -  person Jonas Wilms    schedule 12.04.2021
comment
@JonasWilms, хм, хороший момент. Есть идеи, как изменить функцию для работы с условием? Я много пробовал, но ничего не получается.   -  person zbauman    schedule 12.04.2021


Ответы (1)


Здесь вы столкнулись с несколькими проблемами:

(1) Имена вычисляемых свойств расширены, можно сказать, это ошибка:

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 кажется очень разумным.

person Jonas Wilms    schedule 12.04.2021
comment
Хорошо сказано, это все мне объясняет. Спасибо за Ваш ответ! - person zbauman; 12.04.2021