Tipuri condiționale Typescript cu proprietăți lipsă
Nu pot să înțeleg de ce următorul cod TypeScript eșuează, deși aparent totul ar trebui să fie în regulă:
interface Base { basic: boolean };
interface Super { basic: boolean; extra: boolean };
type Options<T> = T extends Super ? { isSuper: boolean } : { notSuper: boolean }
const baseOptions: Options<{ basic: boolean }> = { notSuper: true }; // ok
const superOptions: Options<{ basic: boolean; extra: boolean }> = { isSuper: true }; // ok
type MakeOptions = <T>() => Options<T>
function withOptions <T>(makeOptions: MakeOptions): void {
const options = makeOptions<T>();
console.log(options.notSuper); // Error: Property 'notSuper' does not exist on type 'Options<T>'.(2339)
console.log(options.isSuper); // Error: Property 'isSuper' does not exist on type 'Options<T>'.(2339)
}
Mă aștept ca options.isSuper să fie undefined | { isSuper: boolean } și options.notSuper să fie undefined | { notSuper: boolean }
În schimb, Typescript elimină aceste proprietăți complet.
Problema este rezolvată atunci când se schimbă în
type Options<T> = T extends Super ? { isSuper: boolean; notSuper?: undefined } : { notSuper: boolean; isSuper?: undefined }
Dar pare inutil.
Teren de joacă
Răspuns #1
În Options<T> așa cum vrei să returneze un obiect cu parametrul isSuper sau notSuper, am adăugat într-o interfață pentru ambele.
interface IisSuper { isSuper: boolean }
interface InotSuper { notSuper: boolean }
În Options<T>, deoarece poate fi oricare dintre interfețele menționate anterior, am creat un tip de uniune pentru acesta numit TSuper.
type Options<T> = T extends Super ? IisSuper : InotSuper
type TSuper = IisSuper | InotSuper
În funcția withOptions<T> am folosit cuvântul cheie as, care este o Asumare de Tip (Type Assertion) ce îi spune compilatorului să considere obiectul ca fiind de un alt tip decât tipul pe care compilatorul îl deduce pentru obiect. În acest caz, este uniunea dintre IisSuper și InotSuper în care Options<T> poate exista.
Deoarece Typescript nu poate garanta tipul lui options la rulare, iar tu vrei să accesezi fie notSuper, fie isSuper, trebuie să restrângi domeniul folosind cuvântul cheie in pentru options pentru a accesa parametrul în tipul dorit.
function withOptions <T>(makeOptions: MakeOptions): void {
const options = makeOptions<T>() as TSuper;
if('notSuper' in options){
console.log(options.notSuper);
}
else if('isSuper' in options){
console.log(options.isSuper);
}
}
Cod final:
interface Base { basic: boolean };
interface Super { basic: boolean; extra: boolean };
interface IisSuper { isSuper: boolean }
interface InotSuper { notSuper: boolean }
type Options<T> = T extends Super ? IisSuper : InotSuper
type MakeOptions = <T>() => Options<T>
type TSuper = IisSuper | InotSuper
const baseOptions: Options<{ basic: boolean }> = { notSuper: true };
const superOptions: Options<{ basic: boolean; extra: boolean }> = { isSuper: true };
function withOptions <T>(makeOptions: MakeOptions): void {
const options = makeOptions<T>() as TSuper;
if('notSuper' in options){
console.log(options.notSuper);
}
else if('isSuper' in options){
console.log(options.isSuper);
}
}
