Powoli zaczynamy naukę TypeScripta. Przykłady na początku będą takie sobie, musimy trochę się rozpędzić, aby móc coś ciekawego zrobić. Dzisiaj – readonly.
Ok, najpierw coś sobie napiszemy, później przelecimy kilka przykładów z internetu. Interfejs Todo:
interface Todo {
title: string;
}
Teraz takie coś, interfejsów ani typów generycznych nie tłumaczyliśmy, ale to tak proste przykłady, że ogarniemy w mig, resztę później wytłumaczymy. Pomyślmy, co z tego wyjdzie po kompilacji:
let todo1: Todo = {title: "Abc"};
let todo2: Todo = Object.freeze({title: "Abc"});
let todo3: Readonly<Todo> = Object.freeze({title: "Abc"});
let todo4 = Object.freeze<Todo>({title: "Abc"});
let todo5: Readonly<Todo> = {title: "Abc"};
Mała podpowiedź, todo1 i todo5 będą wyglądać identycznie. Todo od 2 do 4 też, ale inaczej. Ok, oto kod po kompilacji:
"use strict";
let todo1 = { title: "Abc" };
let todo2 = Object.freeze({ title: "Abc" });
let todo3 = Object.freeze({ title: "Abc" });
let todo4 = Object.freeze({ title: "Abc" });
let todo5 = { title: "Abc" };
Czyli widzimy, Readonly jest dla TSa. Jak chcemy, aby ten obiekt po kompilacji dalej był tylko do odczytu to musi być freeze użyte. I jedno z drugim nie jest połączone.
Ok, ale to był generyczny typ pomocniczy Readonly, teraz co z modyfikatorem. Cóż, tu mamy przykład:
class Employee {
readonly empCode: number;
empName: string;
constructor(code: number, name: string) {
this.empCode = code;
this.empName = name;
}
}
let emp = new Employee(10, "John");
emp.empCode = 20; //Compiler Error
emp.empName = 'Bill';
Mam nadzieję, że w poprzedniej lekcji uwrażliwiłem na typy zagnieżdżone i że tam zawsze jest jakiś edge case (nawet w zwykłym JS). Zawsze coś się znajdzie, choćby to:
type Person = {
readonly name : {
fullName : string,
readonly nickName: String
}
}
const person:Person = {
name: {
fullName: "Hoshikage Kisame",
nickName: "Blackfish"
}
}
person.name = {}; // compile time error
person.name.fullName = "Namikaze Mintao"; // okay
person.name.nickName = "Nikka" // compile time error
Jak widać potrzebne jest readonly przed nazwą property mającą wartość jako obiekt (broni przed nadpisaniem tego property) i readonly przed każdym kluczem obiektu zagnieżdżonego, jeżeli nie chcemy dawać możliwości ich nadpisywania.
Zmienne klasowe i zmienne instancji mogą być readonly i albo od razu przypisujemy wartość (klasowe) albo w konstruktorze:
class FooClz {
readonly bar = 1;
readonly baz: string;
constructor(name: string) {
this.baz = name;
}
}
let fooObj = new FooClz("John");
Swoją drogą myliłem się, jedna i druga będzie dla instancji tak to wygląda po kompilacji:
class FooClz {
constructor(name) {
this.bar = 1;
this.baz = name;
}
}
let fooObj = new FooClz("John");
Do robienia statycznych pól mamy słówko static:
class FooClz {
static readonly bar = 1;
readonly baz: string;
constructor(name: string) {
this.baz = name;
}
}
let fooObj = new FooClz("John");
Tak to wygląda po kompilacji:
class FooClz {
constructor(name) {
this.baz = name;
}
}
FooClz.bar = 1;
let fooObj = new FooClz("John");
Jeżeli robiliśmy kurs PHPa i ogarniamy constructor promotion (albo programowaliśmy w jakimś języku z OOP z prawdziwego zdarzenia) to takie coś też nie będzie nas dziwić:
class FooClz {
static readonly bar = 1;
constructor(readonly name: string) {
}
}
let fooObj = new FooClz("John");
I jak widać z readonly też to można łączyć.
Ok – znalazłem fajny patent na interfejs, który wymusza, aby wartości były readonly:
interface IMarkReadOnly
{
readonly [key :string] : any;
}
class Foo implements IMarkReadOnly
{
name : string;
published: boolean; // Error: must be initilized here or in constructor
constructor(){
this.name = "Okay";
}
}
To także wymusza, aby klucze były stringami, ich wartości są dowolne.
Ok, to po kolej – readonly sprawia, że każda property musi być readonly. A ten zapis z indeksem? Tu przykład:
interface ArrLikeObj {
[index: number] : number;
length: number;
}
let obj : ArrLikeObj = {
0: 1,
1: 2,
length: 2
}
Tu mamy interfejs, który wymusza, że ma być atrybut length, który zwraca number oraz numeryczne atrybuty (tylko takie), które zwracają number. Tam mamy po prostu połączenie tych dwóch rzeczy.
Zanim przejdziemy dalej – pobawmy się tymi kodami, spróbujmy coś popsuć (VSCode zalecam, on będzie pokazywał błędy w czasie rzeczywistym).