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.