Jenis bersyarat dalam TypeScript

65

Saya bertanya-tanya apakah saya dapat memiliki tipe bersyarat dalam TypeScript?

Saat ini saya memiliki antarmuka sebagai berikut:

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

Tapi saya ingin menghapus errorText, dan hanya memilikinya ketika isValidadalah falsesebagai diperlukan properti.

Saya berharap saya bisa menulisnya sebagai antarmuka berikut:

interface ValidationResult {
  isValid: true;
}

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

Tapi seperti yang Anda tahu, itu tidak mungkin. Jadi, apa idemu tentang situasi ini?

Arman
sumber
Apakah yang Anda maksud, saat isValidini false?
Kinerja Tertentu
18
isValid redundan kalau begitu. Anda mungkin juga memiliki errorText, dan kemudian jika errorText adalah nol, tidak ada kesalahan.
MTilsted
Ya, @MTilsted, Anda benar, tetapi kami harus menyimpannya karena kode warisan kami.
Arman

Jawaban:

89

Salah satu cara untuk memodelkan jenis logika ini adalah dengan menggunakan tipe union, sesuatu seperti ini

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" }
}

Compiler kemudian dapat mempersempit jenis berdasarkan bendera boolean

const getErrorTextIfPresent = (r: ValidationResult): string | null => {
  return r.isValid ? null : r.errorText
}
bug
sumber
7
Jawaban bagus. Dan menarik bahwa kompiler dapat benar-benar tahu bahwa rharus mengetik di Invalidsini.
sleske
1
Ini disebut serikat terdiskriminasi. Hal-hal keren: typescriptlang.org/docs/handbook/…
Umur Kontacı
41

Untuk menghindari membuat banyak antarmuka yang hanya digunakan untuk membuat yang ketiga, Anda juga dapat berganti secara langsung, dengan yang type:

type ValidationResult = {
    isValid: false;
    errorText: string;
} | {
    isValid: true;
};
Performa Tertentu
sumber
20

The serikat ditunjukkan oleh bug adalah bagaimana saya sarankan penanganan ini. Meskipun demikian, Typefrip memang memiliki sesuatu yang dikenal sebagai " tipe bersyarat ," dan mereka dapat menangani ini.

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


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

Ini ValidationResult(yang sebenarnya ValidationResult<boolean>disebabkan oleh parameter default) setara dengan gabungan yang dihasilkan dalam jawaban bug atau dalam jawaban SpecificPerformance , dan dapat digunakan dengan cara yang sama.

Keuntungannya di sini adalah Anda juga bisa memberikan ValidationResult<false>nilai yang diketahui , dan kemudian Anda tidak perlu menguji isValidkarena akan diketahui falsedan errorStringdiketahui ada. Mungkin tidak perlu untuk kasus seperti ini — dan tipe kondisional mungkin rumit dan sulit untuk di-debug, jadi mereka mungkin tidak boleh digunakan secara tidak perlu. Tapi Anda bisa, dan itu sepertinya layak disebut.

KRyan
sumber
3
Saya yakin ini kadang sangat membantu. Tapi, bagi saya, sintaksinya terlihat sangat buruk.
Peilonrayz
3
@ Peilonrayz Eh, ini memiliki konsistensi yang baik dengan pengodean naskah lain, dan extendsmerupakan operator yang tepat untuk digunakan. Dan memiliki sangat kuat, terutama karena Anda juga dapat menggunakannya untuk menggali ke dalam jenis: type SecondOf<T> = T extends Pair<any, infer U> ? U : never;.
KRyan
@Ryan Aku sudah memikirkan itu. Apakah itu pada dasarnya hanya berarti SecondOf<number>'mengembang' Pair<any, number>? Saya kira ungkapan "jangan menilai buku dari sampulnya" relevan di sini.
Peilonrayz
@Peilonrayz Ah, tidak; sebaliknya. SecondOf<Pair<any, number>>mengevaluasi ke number. SecondOf<number>mengevaluasi ke never, karena number extends Pair<any, infer U>salah, karena numbertidak memperpanjangPair
KRyan