Wprowadzenie do TypeScript
Opublikowany: 2020-10-29Pierwsze języki, w których programowałem, to C i Java. W tych językach za każdym razem, gdy definiujesz zmienną, musisz określić jej typ. Jeśli spróbujesz przypisać mu wartość innego typu, kompilator narzeka:
// main.c int main() { int x = "two"; return 0; } > cc main.c -o main // warning: initialization makes integer from pointer without a castProblem polega na tym, że z powodu mojego braku doświadczenia wiele skarg, które otrzymałem od kompilatora, wyglądało na tajemnicze i złożone. Dlatego w końcu spojrzałem na kompilator jako na narzędzie ograniczające moją kreatywność, podczas gdy w rzeczywistości ma to być partner, który jest do pomocy.

Później w mojej karierze zacząłem używać niektórych języków programowania bez silnego typowania, takich jak JavaScript czy PHP. Myślałem, że są całkiem fajne: bardzo łatwo było szybko prototypować rzeczy bez konieczności radzenia sobie z wybrednym kompilatorem.
Jak już wiesz, języki programowania, na których oparty jest WordPress, to PHP i JavaScript. Oznacza to, że prawdopodobnie jesteś przyzwyczajony do kodowania wtyczek i motywów bez kompilatora pilnującego twoich pleców. To tylko Ty, Twoje umiejętności i Twoja kreatywność. No i błędy takie jak ten:
const user = getUser( userId ); greet( user.name ); // Uncaught TypeError: user is undefined Jeśli masz dość undefined błędów w kodzie, czas dodać kompilator do przepływu pracy. Przyjrzyjmy się, czym jest TypeScript i jak pozwala nam poprawić jakość naszego oprogramowania o kilka rzędów wielkości.
Co to jest TypeScript
TypeScript to język programowania oparty na JavaScript, który został stworzony w celu dodania silnego i statycznego typowania. Typy TypeScript pozwalają nam opisać kształt naszych obiektów i zmiennych, co skutkuje lepiej udokumentowanym i bardziej niezawodnym kodem. Sam TypeScript będzie odpowiedzialny za walidację wszystkiego, co robimy.
Zgodnie z projektem, TypeScript jest nadzbiorem JavaScript. Oznacza to, że każdy kod napisany w zwykłym JavaScript jest z definicji również poprawnym TypeScript. Ale odwrotnie nie jest prawdą: jeśli używasz funkcji specyficznych dla TypeScript, wynikowy kod nie jest prawidłowym kodem JavaScript, dopóki go nie przetranspilujesz.
Jak działa TypeScript
Aby zrozumieć, jak działa TypeScript, użyjemy Playground , małego edytora, w którym możemy napisać kod TypeScript i zobaczyć, co powie nam o nim kompilator.
Będąc nadzbiorem JavaScript, pisanie kodu TypeScript jest niezwykle łatwe. Zasadniczo następujący kod JavaScript:
let user = "David"; let age = 34; let worksAtNelio = true; console.log( user, age, worksAtNelio );to także kod TypeScript. Możesz go skopiować i wkleić w TypeScript Playground, a zobaczysz, że się kompiluje. Więc co jest w tym takiego specjalnego? Oczywiście jego rodzaje. W JavaScript możesz wykonać następujące czynności:
let user = "David"; let age = 34; let worksAtNelio = true; user = { name: "Ruth" }; console.log( user, age, worksAtNelio );ale spowoduje to błąd w TypeScript. Po prostu wypróbuj go w Placu zabaw, a zobaczysz następujący błąd:
Type '{ name: string; }' is not assignable to type 'string'.Więc o co w tym wszystkim chodzi?
TypeScript może automatycznie wywnioskować typ zmiennej. Oznacza to, że nie musimy wyraźnie mówić „hej, ta zmienna to łańcuch” (albo liczba, wartość logiczna, czy cokolwiek innego); zamiast tego patrzy na wartość, którą otrzymał jako pierwszy i na tej podstawie wnioskuje o jej typie.

W naszym przykładzie, kiedy zdefiniowaliśmy zmienną user , przypisaliśmy jej ciąg tekstowy "David" , więc TypeScript wie, że user jest (i zawsze powinien być) string . Problem polega na tym, że nieco później próbujemy zmienić typ naszej zmiennej user , przypisując jej obiekt, który ma pojedynczą właściwość ( name ), której wartością jest ciąg "Ruth" . Oczywiście ten obiekt nie jest string , więc TypeScript narzeka i informuje nas, że przypisanie nie może zostać wykonane.
Istnieją dwie możliwe drogi, aby to naprawić:
// Option 1 let user = "David"; user = "Ruth"; // OK. // Option 2 let user = { name: "David" }; user = { name: "Ruth" };Bądźmy szczerzy przy definiowaniu typów naszych zmiennych
Ale wnioskowanie o typie jest tylko po to, aby nam pomóc. Jeśli chcemy, możemy jawnie wskazać TypeScript typ zmiennej:
let user: string = "David"; let age: number = 34; let worksAtNelio: boolean = true; console.log( user, age, worksAtNelio );który renderuje dokładnie ten sam wynik, co typy wywnioskowane przez TypeScript. Teraz nasuwa się pytanie: jeśli TypeScript może automatycznie wywnioskować typy, dlaczego potrzebujemy tej funkcji?
Z jednej strony jawne określanie typów może służyć lepszemu dokumentowaniu naszego kodu (co stanie się jeszcze jaśniejsze w następnej sekcji). Z drugiej strony pozwala nam rozwiązać ograniczenia systemu wnioskowania o typach. Tak, dobrze to przeczytałeś: są przypadki, w których wnioskowanie nie jest zbyt dobre, a najlepsze, co może zrobić TypeScript, to powiedzieć nam, że „no cóż, nie wiem, czym dokładnie powinna być ta zmienna; Myślę, że to może być wszystko!”
Rozważmy następujący przykład:
const getName = ( person ) => person.name; let david = { name: "David", age: 34 }; console.log( getName( david ) ); // Parameter 'person' implicitly has an 'any' type. W tym przypadku definiujemy funkcję getName , która otrzymuje parametr i zwraca wartość. Zwróć uwagę, ile rzeczy zakładamy w tak prostej funkcji: oczekujemy obiektu ( person ) z co najmniej jedną właściwością o nazwie name . Ale tak naprawdę nie mamy żadnej gwarancji, że ktokolwiek wywoła tę funkcję, użyje jej właściwie. Ale nawet jeśli tak, nadal nie wiemy, jaki jest wynikowy typ tej funkcji. Jasne, możesz założyć, że nazwa będzie ciągiem znaków , ale wiesz o tym tylko dlatego, że jesteś człowiekiem i rozumiesz znaczenie tego słowa. Ale spójrz na następujące przykłady:

getName( "Hola" ); // Error getName( {} ); // Returns undefined getName( { name: "David" } ); // Returns a string getName( { name: true } ); // Returns a booleanza każdym razem, gdy wywołujemy funkcję, otrzymujemy inny wynik! Tak więc, mimo że TypeScript pilnuje naszych pleców, wciąż napotykamy klasyczne problemy z JavaScriptem: ta funkcja może odebrać wszystko i może wszystko zwrócić.

Rozwiązaniem, zgadłeś, jest jawne opisanie funkcji.
Zdefiniuj własne typy
Ale zanim to zrobimy, zobaczmy kolejną dodatkową funkcję TypeScript: niestandardowe typy danych. Jak dotąd prawie wszystkie nasze przykłady wykorzystywały podstawowe typy, takie jak string , number , boolean , itp., które moim zdaniem są dość łatwe do zrozumienia. Widzieliśmy również bardziej złożone struktury danych, takie jak obiekty:
let david = { name: "David", age: 34 };ale nie zwracaliśmy zbytniej uwagi na jego typ, jak wywnioskował TypeScript, prawda? Widzieliśmy tylko przykład nieprawidłowego przypisania spowodowanego niezgodnością wpisywania:
let user = "David"; user = { name: "Ruth" }; // Type '{ name: string; }' is not assignable to type 'string'.co w jakiś sposób sugerowało, że typem obiektu był „ {name:string;} ”, co można odczytać jako „to jest obiekt z właściwością o nazwie nazwa typu string ”. Jeśli w ten sposób są definiowane typy obiektów, następujące powinno być prawidłowym TypeScript:
let david: { name: string, age: number } = { name: "David", age: 34 };i rzeczywiście tak jest. Ale jestem pewien, że zgodzisz się, że nie jest to wygodne. Na szczęście możemy tworzyć nowe typy w TypeScript.
Ogólnie rzecz biorąc, typ niestandardowy to w zasadzie zwykły typ TypeScript o niestandardowej nazwie:
type Person = { name: string; age: number; }; let david: Person = { name: "David", age: 34 }; Fajne hę? Teraz, gdy mamy już typ Person , możemy łatwo opisać naszą funkcję getName i jasno określić typ naszego parametru wejściowego:
const getName = ( person: Person ) => person.name; A ta prosta aktualizacja dostarcza TypeScriptowi wielu informacji! Na przykład może teraz wywnioskować, że typem wyniku tej funkcji jest string , ponieważ wie na pewno, że atrybut name obiektu Person również jest string :
let david: Person = { name: "David", age: 34 }; let davidName = getName( david ); davidName = 2; // Type 'number' is not assignable to type 'string'.Ale, jak zawsze, możesz wyraźnie opisać typ wyniku, jeśli chcesz:
const getName = ( person: Person ): string => person.name;Więcej rzeczy z typami TypeScript…
Na koniec chciałem podzielić się z wami kilkoma interesującymi osiągnięciami, z których można skorzystać, jeśli zdefiniujesz własne typy w TypeScript.
TypeScript jest bardzo wymagający, jeśli chodzi o przypisywanie wartości do zmiennych. Jeśli jawnie zdefiniowałeś typ określonej zmiennej, możesz przypisać tylko wartości, które dokładnie pasują do tego typu. Zobaczmy to na kilku przykładach:
type Person = { name: string; age: number; }; let david: Person = { name: "David", age: 34 }; // OK let ruth: Person = { name: "Ruth" }; // Property 'age' is missing in type '{ name: string; }' but required in type 'Person' let toni: Person = { name: "Toni", age: 35, gender: "M" }; // Type '{ name: string; age: number; gender: string; }' is not assignable to type 'Person'. Object literal may only specify known properties, and 'gender' does not exist in type 'Person'. Jak widać, zmienna Person akceptuje tylko obiekty, które są w pełni zgodne z typem Person . Jeśli brakuje niektórych atrybutów ( ruth nie ma age ) lub jest ich więcej niż tych zawartych w typie ( toni ma gender ), TypeScript zgłosi skargę i wywoła błąd niezgodności typów.
Ale jeśli mówimy o wywoływaniu funkcji, sprawy mają się inaczej! Typy argumentów w funkcji nie są dokładnym wymaganiem, ale określają minimalny interfejs, z którym muszą być zgodne parametry. Wiem, wiem, to zbyt abstrakcyjne. Przyjrzyjmy się następującemu przykładowi, który, jak sądzę, wyjaśni sprawę:
type NamedObject = { name: string; }; const getName = ( obj: NamedObject ): string => obj.name; const ruth = { name: "Ruth" } getName( ruth ); // OK const david = { name: "David", age: 34 }; getName( david ); // OK const toni = { firstName: "Toni" }; getName( toni ); // Argument of type '{ firstName: string; }' is not assignable to parameter of type 'NamedObject'. Property 'name' is missing in type '{ firstName: string; }' but required in type 'NamedObject'. Jak widać, przedefiniowaliśmy funkcję getName jako coś bardziej ogólnego: teraz przyjmuje obiekt NamedObject , to znaczy obiekt, który musi mieć atrybut nazwany name typu string . Używając tej definicji, widzimy, jak ruth i david doskonale spełniają te wymagania (obaj mają atrybut name ), ale toni nie, ponieważ nie ma atrybutu oczekiwanego name .
Wniosek
TypeScript to język programowania, który rozszerza JavaScript, dodając definicje typów statycznych. Dzięki temu jesteśmy znacznie bardziej precyzyjni przy definiowaniu danych, z którymi pracujemy, a co ważniejsze, pomaga nam szybciej wykrywać błędy.
Koszt integracji TypeScriptu ze stosem programistycznym jest stosunkowo niewielki i można go wykonać stopniowo. Ponieważ cały kod JavaScript jest z definicji TypeScript, przełączenie z JavaScript na TypeScript jest automatyczne – możesz dodawać typy i upiększać swój kod krok po kroku.
Jeśli podobał Ci się ten post i chcesz dowiedzieć się więcej, udostępnij go i daj mi znać w sekcji komentarzy poniżej.
Polecane zdjęcie King's Church International na Unsplash.
