Ok, przypomnimy sobie przykład z lekcji o as const i w końcu odpowiemy sobie na pytanie dlaczego (do niedawna sam tego nie wiedziałem). Zaczynajmy.
Ok, oto ten przykład, który sprawił nam tyle niespodzianek:
function handleRequest(url: string, method: 'GET' |'POST'){
}
Tu sobie wyjaśnijmy czym jest literal type. Tutaj w kontekście dwóch różnych napisów jako parametr, i tylko one są akceptowane, ale możemy też użyć liczb i jako wartość zwracana.
Funkcja compare to chyba dobry przykład takiego przypadku, gdzie właśnie literal type się sprawdzi:
function compareNums(num1: number, num2: number): -1|0|1 {
if(num1 > num2)
return 1;
if(num1 = num2)
return 0;
return -1;
}
Ale mniejsza o tę funkcję. Wracając do naszego przykładu, zobaczmy na ten kod:
let url1 = 'www.google.com';
let method1 = 'GET' ;
handleRequest(url1, method1); //źle
Teraz const bez adnotacji:
let url1 = 'www.google.com';
const method1 = 'GET' ;
handleRequest(url1, method1); //teraz ok
Teraz const z adnotacją jako string:
let url1 = 'www.google.com';
const method1: string = 'GET' ;
handleRequest(url1, method1); //znowu źle
Teraz poprawna adnotacja:
let url1 = 'www.google.com';
const method1: 'GET'| 'POST' = 'GET' ;
handleRequest(url1, method1); //znowu ok
Nas interesuje dlaczego, ale na razie dodam, że takie adnotacje robi się poprzez type:
type ValidMethod = 'GET'| 'POST';
let url1 = 'www.google.com';
const method1: ValidMethod = 'GET' ;
handleRequest(url1, method1); //dalej ok
Ok, kolejne przykłady:
let req1 = {url: 'www.google.com', method: 'GET'};
handleRequest(req1.url, req1.method); //źle
Dobra, a teraz tak:
let req1 = {url: 'www.google.com', method: 'GET'};
handleRequest(req1.url, req1.method as ValidMethod); //teraz ok
Teraz z as const:
let req1 = {url: 'www.google.com', method: 'GET'} as const;
handleRequest(req1.url, req1.method); //też ok
Zanim to sobie wyjaśnimy jeszcze przykład, gdzie tylko as const nas poratuje. Najpierw problem:
const req2 = ["www.google.com", 'GET'];
handleRequest(...req2); //źle
Teraz rozwiązanie:
const req2 = ["www.google.com", 'GET'] as const;
handleRequest(...req2); //ok
No dobra, mamy jeszcze utility type Parameters, ostatecznie możemy się nim wyręczyć:
const req2 = ["www.google.com", 'GET'] as Parameters<typeof handleRequest>;
handleRequest(...req2); //też ok
Ok, z tym parameters to może jeszcze ogarniamy, że hadleRequest ma literal type GET|POST, zaś w tablicy mamy string (tak sobie TS wnioskuje) i jako że nie ma żadnego mechanizmu powiedzenia, że ten GET to jest typ GET|POST, a nie jakieś byle jakie 3 literki i dopiero Parameters nas ratuje… To może jeszcze jakoś ogarniamy.
Ale czemu ten as const nas ratuje? Ja to sobie tłumaczyłem mutowalnością, tym, że można jakąś metodą zmienić obiekt w tablicy/obiekcie, ale nie miałem racji.
Ok, rzućmy okiem na takie coś:
let xNum = 1;
type xNumType = typeof xNum;
//type xNumType = number
Tak typescript domyśla się typu z leta. Teraz z const, ale bez adnotacji:
const yNum = 2;
type yNumType = typeof yNum;
//type yNumType = 2
Tu już widzimy, że literalnie to rozumie. Teraz z adnotacją:
const zNum: number = 3;
type zNumType = typeof zNum;
//type zNumType = number
No a teraz wbijmy sobie do głowy – let/const po lewej, literał obiektu/tablicy po prawej, tak? Ale są przypadki, gdy używamy literałów (np. wołając funkcję) ale nijak nie wybieramy czy let czy const.
Nie, stop, źle tłumaczę, w takim razie to błędu nie powinno dawać, a daje:
const req2 = ["www.google.com", 'GET'];
handleRequest(...req2); //źle
No tylko właśnie, tam mamy zachowanie dla typów prostych pokazane z tymi constami. Tu mamy typ złożony. I tutaj const, ten pierwszy, zapewnia, że nie można innej referencji przypisać do req2.
Teraz dodajmy drugiego consta:
const req2 = ["www.google.com", 'GET'] as const;
handleRequest(...req2); //ok
Ten drugi const nam zapewnia, że… no właśnie, chciałem powiedzieć, że te typy będą traktowane literalnie, ale zobaczyłem, co mi ten as const zwraca:
const req2 = ["www.google.com", 'GET'] as const;
//const req2: readonly ["www.google.com", "GET"]
Nie, ok literalnie, tylko przez readonly xD Czyli de facto obie teorie miały w sobie ziarnko prawdy.
No to nie jest łatwe. Ale poukładajmy to sobie w głowie jeszcze raz. Oto wersja bez as const:
const req2: readonly[string, ValidMethod] = ["www.google.com", 'GET'];
handleRequest(...req2); //ok
I to samo bez readonly:
const req2: [string, ValidMethod] = ["www.google.com", 'GET'];
handleRequest(...req2); //też ok
As const wymusza niemutowalność poszczególnych pól (nie tylko referencji req2), ale to nie brak mutowalności był tutaj problemem. A to, że nigdy nigdzie nie powiedzieliśmy, że drugie to będzie ValidMethod.
Ok, poznaliśmy kilka dziwactw TSa, chyba już coraz więcej umiemy, także teraz nic tylko kontynuować. Więcej TSa niedługo.