sučelje - Typescript unija i presjek daju neintuitivne i pogrešne poruke o pogreškama
Ova izvezena vrsta savršeno provjerava sve, ali daje pogrešnu poruku o pogrešci prilikom provjere:
export type Animal = RequireSwimOrFly & RequireColorOrSizeOrVolume & (CantHaveBothColorOrSize | CanHaveNeitherColorOrSize)
const myAnimal: Animal = {
swim: "greatly",
color: "yellow",
size: "large",
}
Daje sljedeću pogrešku:
Property 'fly' is missing in type '{ swim: string; color: string; size: string; }' but required in type 'Required<Pick<IAnimal, "fly">>'.(2322)
Ali to nije ispravna provjera! Prihvatljivo je NEMAJATI krila, jer ima plivanje. Prava pogreška je imati boju i veličinu istovremeno. Postoji li način da se za buduće programere daju intuitivnije pogreške?
Evo radne verzije na TS Playgroundu
Koristim više vrsta da provjerim više uvjeta:
type RequireSwimOrFly = RequireAtLeastOne<IAnimal, 'swim' | 'fly'>
type RequireColorOrSizeOrVolume = RequireAtLeastOne<IAnimal, 'color' | 'size' | 'volume'>
type CantHaveBothColorOrSize = RequireOnlyOne<IAnimal, 'color' | 'size'>
type CanHaveNeitherColorOrSize = Omit<IAnimal, 'color' | 'size'> & { color?: never, size?: never }
S zahtjevima definiranim kako slijedi:
type RequireAtLeastOne<T, Keys extends keyof T = keyof T> =
Pick<T, Exclude<keyof T, Keys>>
& {
[K in Keys]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<Keys, K>>>
}[Keys]
type RequireOnlyOne<T, Keys extends keyof T = keyof T> =
Pick<T, Exclude<keyof T, Keys>>
& {
[K in Keys]-?: Required<Pick<T, K>> & Partial<Record<Exclude<Keys, K>, undefined>>
}[Keys]
I sučelje:
interface IAnimal {
swim?: string;
fly?: string;
color?: string;
size?: string;
volume?: string;
}
Odgovor #1
U konačnici, vaš Animal tip je veliki unija tipova objekata; presjeci se obično distribuiraju preko unija, a presjek objekata se općenito može sažeti zajedno. Možete čak napisati pomoćni "identity" korisnički tip da to eksplicitno napravite kad ga gledate u IntelliSenseu:
type Id<T> = T extends infer U ? { [K in keyof U]: U[K] } : never;
export type Animal = Id<RequireSwimOrFly & RequireColorOrSizeOrVolume & (
CantHaveBothColorOrSize | CanHaveNeitherColorOrSize
)>
što vam daje ovo:
/* type Animal = {
color: string; size?: undefined; volume?: string | undefined;
swim: string; fly?: string | undefined;
} | {
color?: undefined; size: string; volume?: string | undefined;
swim: string; fly?: string | undefined;
} | {
color: string; size?: undefined; volume: string;
swim: string; fly?: string | undefined;
} | {
color?: undefined; size: string; volume:
string; swim: string; fly?: string | undefined;
} | {
color?: undefined; size?: undefined; volume: string;
swim: string; fly?: string | undefined;
} | {
color: string; size?: undefined; volume?: string | undefined;
fly: string; swim?: string | undefined;
} | {
color?: undefined; size: string; volume?: string | undefined;
fly: string; swim?: string | undefined;
} | {
color: string; size?: undefined; volume: string;
fly: string; swim?: string | undefined;
} | {
color?: undefined; size: string; volume: string;
fly: string; swim?: string | undefined;
} | {
color?: undefined; size?: undefined; volume: string;
fly: string; swim?: string | undefined;
} */
Ako kompilatoru date vrijednost koja nije tipa Animal, to znači da se ne može dodijeliti nijednom članu unije. Ona ne uspijeva kod svakog od njih. Cjelovito izvješće o tome zašto je ta vrijednost neprikladna bilo bi vrlo, vrlo dugo, budući da kompilator spominje svakog člana unije i način na koji vaša vrijednost ne odgovara njemu. Nešto poput:
const myAnimal: Animal = { swim: "greatly", color: "yellow", size: "large", } // error!
/* Type { swim: string; color: string; size: string; } is not assignable to Animal.
- It can't be a { ✂ 1st union member ✂ } because its 'size' property is 'string' and not 'undefined'
- It can't be a { ✂ 2nd union member ✂ } because its 'color' property is 'string' and not 'undefined'
- It can't be a { ✂ 3rd union member ✂ } because its 'size' property is 'string' and not 'undefined'
- It can't be a { ✂ 4th union member ✂ } because its 'color' property is 'string' and not 'undefined'
- It can't be a { ✂ 5th union member ✂ } because its 'size' property is 'string' and not 'undefined'
- It can't be a { ✂ 6th union member ✂ } because its 'fly' property is 'undefined' and not 'string'
- It can't be a { ✂ 7th union member ✂ } because its 'fly' property is 'undefined' and not 'string'
- It can't be a { ✂ 8th union member ✂ } because its 'fly' property is 'undefined' and not 'string'
- It can't be a { ✂ 9th union member ✂ } because its 'fly' property is 'undefined' and not 'string'
- It can't be a { ✂ 10th union member ✂ } because its 'fly' property is 'undefined' and not 'string'
*/
Neki sindikati imaju tisuće članova, pa nije realno očekivati da prevoditelj pruži potpuni izvještaj o svim načinima na koje vrijednost ne može biti dodijeljena njima. Mora učiniti nešto drugo.
Sada, mogao bi pokušati pronaći „najbližeg” člana unije vrijednosti prema nekoj metričkoj mjeri, a zatim prijaviti samo tamo pogrešku. Ili bi možda mogao pokušati pronaći „najčešći” razlog neuspjeha i prijaviti njega. Ili bi možda mogao primijeniti neku drugu, više korisnički prihvatljivu heuristiku. Ali ne radi ništa od toga; bira posljednjeg člana unije i prijavljuje njega:
const myAnimal: Animal = { swim: "greatly", color: "yellow", size: "large", } // error!
/* Type '{ swim: string; color: string; size: string; }' is not assignable to type 'Animal'.
It is missing the following properties from type '{ ✂ 10th union member ✂ }' : volume, fly */
Ova poruka nije pogrešna. Samo što nije osobito prosvjetljujuća ili intuitivna.
Na GitHubu je otvoren problem na microsoft/TypeScript#4451 tražeći nešto bolje, ali je zatvoren kao 'Neće se popraviti', a glavni objašnjavajući komentar bio je da ne bi bilo očito kako napisati dobar algoritam ovdje.
Dakle, nažalost, tako stvari stoje. Dobiješ neku poruku o pogrešci koja ispravno objašnjava dio razloga zašto je tvoja vrijednost loša, ali ne nužno "taj" razlog kojeg bi dao pametan čovjek.
Poveznica na Playground za kod
