Staramy się osiągnąć mistrzostwo w TypeScript, a to łatwe nie jest. Mimo wszystko, warto, ale musimy się przegryźć przez naprawdę dużo materiału. Do dzieła.

Przypomnijmy sobie dekorator frozen:

function Frozen<T extends { new (...args: any[]): object }>(target: T): T {
    return class extends target {
        constructor(...args: any[]) {
            super(...args);
            Object.freeze(this);
        }
    }
}

Mam nadzieję, że wiemy (pamietamy) dlaczego tak a nie ten głupi przykład, co po internecie krąży i straszy.

Teraz final, jak nie pamiętamy co robi, to wracamy do poprzednich lekcji, tam to opisałem:

function final<T extends { new (...args: any[]): object }>(target: T): T {
    
    return class Final extends target {
        constructor(...args: any[]) {
            if (new.target !== Final) {
                throw new Error('Cannot inherit from final class');
            }
            super(...args);
        }
    };
}

Ok, to teraz pytanie: czemu to działa, ale w odwrotnej kolejności mamy błąd (runtime error, kompilacja ok):

@final
@Frozen
class FinalMe {
    public readonly name: string;

    constructor(name: string){
        this.name = name;
       
    }
}

let finalObj = new FinalMe("John");
console.log(finalObj.name); //john

Jeżeli pamiętamy kolejność aplikowania dekoratorów, to wiemy, że te na dole są aplikowane jako pierwsze. A jeśli nie kopiujemy bezmyślnie, to wiemy że i jeden i drugi zwraca tak naprawdę klasę-dziecko.

Czyli final blokule nie tylko dziedziczenie takie na zasadzie napisania nowej klasy z extends, ale blokuje też wszystkie dekoratory powyżej siebie, aby nie dziedziczyły z tej klasy.

Ok, fajnie, czy to oznacza, że powyżej final nie można już aplikować dekoratorów klasy? Wcale nie – można. Nie można tylko tych, które rozszerzają klasę i zwracają taką klasę-dziecko w miejsce tej klasy.

Można napisać jakiś dekorator, który dopisuje coś do prototypu klasy i umieścić go powyżej final. Albo dekorator, który gdzieś w drzewie DOM wyświetli nam, że klasa zdefiniowana (tu też jest pewien haczyk).

W sumie to zobaczmy taki dekorator:

function alertConstructor(constructor: Function) {
    alert("Class " + constructor.name + " defined!");
  }

Constructor to nic innego jak klasa. Nie robimy tutaj dziedziczenia, więc nie powinno być problemu umieścić to powyżej final:

@alertConstructor
@final
@Frozen
class FinalMe {
    public readonly name: string;

    constructor(name: string){
        this.name = name;
       
    }
}

I nie ma problemu. Natomiast nasz alert wyświetli nam teraz napis Class Final defined!. Czy tego się spodziewaliśmy?

Cóż, musimy dogłębnie zrozumieć co tu się dzieje. Albo olać TypeScripta, bo tak wszystko w TS wygląda i tego nie da się ogarnąć po łebkach…