Условные типы в TypeScript

66

Мне было интересно, могу ли я иметь условные типы в TypeScript?

В настоящее время у меня есть следующий интерфейс:

interface ValidationResult {
  isValid: boolean;
  errorText?: string;
}

Но я хочу удалить errorText, и только тогда, когда isValidестьfalse как требуется собственность.

Я хотел бы написать его как следующий интерфейс:

interface ValidationResult {
  isValid: true;
}

interface ValidationResult {
  isValid: false;
  errorText: string;
}

Но, как вы знаете, это невозможно. Итак, что вы думаете об этой ситуации?

Arman
источник
Вы имеете в виду, когда isValidэто false?
CertainPerformance
18
isValid избыточен тогда. Вы могли бы также просто иметь errorText, а затем, если errorText равен нулю, ошибки нет.
MTilsted
Да, @MTilsted, вы правы, но мы должны сохранить его из-за наших устаревших кодов.
Арман

Ответы:

89

Один из способов моделирования такой логики - использовать тип объединения, что-то вроде этого

interface Valid {
  isValid: true
}

interface Invalid {
  isValid: false
  errorText: string
}

type ValidationResult = Valid | Invalid

const validate = (n: number): ValidationResult => {
  return n === 4 ? { isValid: true } : { isValid: false, errorText: "num is not 4" }
}

Затем компилятор может сузить тип на основе логического флага

const getErrorTextIfPresent = (r: ValidationResult): string | null => {
  return r.isValid ? null : r.errorText
}
ошибки
источник
7
Хороший ответ. И удивительно, что компилятор действительно может сказать, что он rдолжен быть Invalidздесь типа .
Слеск
1
Это называется дискриминационные союзы. Довольно классные вещи: typescriptlang.org/docs/handbook/…
Умур Контачи
41

Чтобы избежать создания нескольких интерфейсов, которые используются только для создания третьего, вы также можете чередовать напрямую с type вместо этого:

type ValidationResult = {
    isValid: false;
    errorText: string;
} | {
    isValid: true;
};
CertainPerformance
источник
20

Союз продемонстрировал ошибками , как я рекомендую ее обработки. Тем не менее, Машинопись делает что - то , известное как « условные типы » , и они могут справиться с этим.

type ValidationResult<IsValid extends boolean = boolean> = (IsValid extends true
    ? { isValid: IsValid; }
    : { isValid: IsValid; errorText: string; }
);


declare const validation: ValidationResult;
if (!validation.isValid) {
    validation.errorText;
}

Эта ValidationResult (что на самом деле ValidationResult<boolean>связано с параметром по умолчанию) эквивалентно объединению, созданному в ответе ошибок или в ответе CertainPerformance , и может использоваться таким же образом.

Преимущество здесь в том, что вы также можете передать известное ValidationResult<false>значение, и тогда вам не нужно будет проверять, isValidкак это было бы известно falseи errorStringбудет существовать. Вероятно, в таком случае нет необходимости, и условные типы могут быть сложными и трудными для отладки, поэтому их, вероятно, не следует использовать без необходимости. Но вы могли бы, и это казалось заслуживающим упоминания.

KRyan
источник
3
Я уверен, что это действительно полезно время от времени. Но для меня синтаксис выглядит очень неприятно.
Пейлонрайз
3
@Peilonrayz Да, он хорошо совместим с другими кодировками Typescript и extendsявляется правильным оператором для использования. И это имеет очень мощный, в частности , так как вы можете использовать его , чтобы вырыть на типы: type SecondOf<T> = T extends Pair<any, infer U> ? U : never;.
KRyan
@KRyan Я думал об этом. Значит ли это, что в основном означает SecondOf<number>«расширяется» до Pair<any, number>? Я полагаю, здесь уместно высказывание «не суди книгу по обложке».
Пейлонрайз
@Peilonrayz Ах, нет; скорее наоборот. SecondOf<Pair<any, number>>оценивает до number. SecondOf<number>оценивает never, потому что number extends Pair<any, infer U>является ложным, поскольку numberне распространяется ни на чтоPair
KRyan