Почему использование оператора if-else приводит к ошибке компилятора TypeScript, когда внешне идентичная конструкция тернарного оператора - нет? - PullRequest
8 голосов
/ 08 мая 2020

У меня есть функция, которая должна либо возвращать значение IDBValidKey, либо что-то преобразованное в IDBValidKey. Если я напишу функцию с помощью тернарного оператора, она будет работать нормально, но вызовет ошибку компилятора, если я запишу ее как оператор if-else:

interface IDBValidKeyConvertible<TConverted extends IDBValidKey> {
    convertToIDBValidKey: () => TConverted;
}

function isIDBValidKeyConvertible<TConvertedDBValidKey extends IDBValidKey>(object: unknown): object is IDBValidKeyConvertible<TConvertedDBValidKey> {
    return typeof((object as IDBValidKeyConvertible<TConvertedDBValidKey>).convertToIDBValidKey) === "function";
}

type IDBValidKeyOrConverted<TKey> = TKey extends IDBValidKeyConvertible<infer TConvertedKey> ? TConvertedKey : TKey;

function getKeyOrConvertedKey<TKey extends IDBValidKey | IDBValidKeyConvertible<any>>(input: TKey): IDBValidKeyOrConverted<TKey> {
    if (isIDBValidKeyConvertible<IDBValidKeyOrConverted<TKey>>(input)) {
        return input.convertToIDBValidKey();
    } else {
        return input;
    }
}

function getKeyOrConvertedKeyTernary<TKey extends IDBValidKey | IDBValidKeyConvertible<any>>(input: TKey): IDBValidKeyOrConverted<TKey> {
    return (isIDBValidKeyConvertible<IDBValidKeyOrConverted<TKey>>(input)) ? input.convertToIDBValidKey() : input;
}

getKeyOrConvertedKeyTernary не выдает ошибок, кроме блока else of getKeyOrConvertedKey дает следующую ошибку:

Type 'TKey' is not assignable to type 'IDBValidKeyOrConverted<TKey>'.
  Type 'string | number | Date | ArrayBufferView | ArrayBuffer | IDBArrayKey | IDBValidKeyConvertible<any>' is not assignable to type 'IDBValidKeyOrConverted<TKey>'.
    Type 'string' is not assignable to type 'IDBValidKeyOrConverted<TKey>'.

Разве тернарный оператор и оператор if-else не эквивалентны?

Спасибо!

Ответы [ 2 ]

2 голосов
/ 09 мая 2020

Почему использование оператора if-else приводит к ошибке компилятора TypeScript, если внешне идентичная конструкция тернарного оператора - нет?

Короткий ответ

TypeScript видит if-else как оператор с несколькими выражениями, каждое из которых имеет независимые типы. TypeScript рассматривает тернар как выражение с типом объединения его истинной и ложной сторон. Иногда этот тип объединения становится достаточно широким, чтобы компилятор не жаловался.

Подробный ответ

Разве тернарный оператор и оператор if-else не эквивалентны?

Не совсем.

Разница проистекает из того, что тернарность является выражением . Здесь есть диалог , где Райан Кавано объясняет разницу между тернарным оператором и оператором if / else. Вывод состоит в том, что тип тернарного выражения представляет собой объединение его результатов true и false.

Для вашей конкретной ситуации тип тернарного выражения any. Поэтому компилятор не жалуется. Ваш тернар представляет собой объединение типа input и возвращаемого типа input.convert(). Во время компиляции тип input расширяет Container<any>; поэтому тип возврата input.convert() - any. Так как объединение с any равно any, тип вашего троичного, ну, ну, any.

Быстрое решение для вас - заменить any на unknown дюйм <TKey extends IDBValidKey | IDBValidKeyConvertible<any>. Это приведет к тому, что как if-else, так и тройной вызовут ошибку компилятора.

Упрощенный пример

Вот ссылка на игровую площадку с упрощенным воспроизведением вашего вопроса. Попробуйте изменить any на unknown, чтобы посмотреть, как отреагирует компилятор.

interface Container<TValue> {
  value: TValue;
}

declare function hasValue<TResult>(
  object: unknown
): object is Container<TResult>;

// Change any to unknown.
const funcIfElse = <T extends Container<any>>(input: T): string => {
  if (hasValue<string>(input)) {
    return input.value;
  }

  return input;
};

// Change any to unknown.
const funcTernary = <T extends Container<any>>(input: T): string =>
  hasValue<string>(input)
    ? input.value
    : input;
0 голосов
/ 09 мая 2020

Имеются две совершенно разные проблемы :

  1. В Typescript есть ошибка или ограничение [1], но это противоположность тому, что ваш вопрос предполагает .

    если я напишу функцию с использованием тернарного оператора, она будет работать нормально

    На самом деле, это не нормально. Ошибка для версии if-else верна, а в тернарной версии должна быть такая же ошибка.

    Вы, вероятно, сделали такое предположение, потому что, как и большинство из нас, мы склонны считать, что наш код является верный. Это приводит ко второй проблеме:


Ваш код нелогичен. (Пожалуйста, извините за резкость.)

Я думаю (с теперь упрощенным кодом в вопросе), что №2 должно быть легче увидеть. Но когда у меня появится немного свободного времени, я постараюсь объяснить подробно.


[1] Есть вероятность, что это не ошибка Typescript, а ожидаемое поведение из-за некоторого тонкого не- эквивалентность между if-else и ? :, о которой я не знаю. Я не удивлюсь, потому что Javascript имеет множество устаревших странностей. [РЕДАКТИРОВАТЬ: См. ответ Шона Латтина , который пришел, когда я печатал свой.]

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...