25 Sep

HypeScript


A múltkori mobilos cikkre megkaptam, hogy inkább az Ionic 2-re vagy React Native-ra kellene fókuszálnom. A dolgot megfogadom, ám ahhoz, hogy az Ionic 2-re rátérhessünk, nem ártana végigjárni az utat hozzá. Ehhez az első lépés a typescript lesz, utána jöhet az Angular2, majd az Ionic 2/Electron és/vagy a Unity-re is sor kerülhet, C# helyett typescript alapon.

typescript-cover-image



Na most mi is az a typescript, miért is jó nekünk és miért is lesz rá szükségünk a későbbiekben?

Haladjunk szép sorban a válaszokkal: A TypeScript a JavaScript egy úgynevezett supersetje, tehát a JavaScript kibővítése, típusokkal felvértezve és ami a lényeg, hogy sima mezei JavaScriptre fordul, amit a böngészők szó nélkül megesznek.

Fordul?? 

Igen, a TypeScriptben írt kódot le kell fordítanunk, ami ugyan nem egy gradle build, hogy percekig tartson, de legalább elindultunk a fordítások közti index olvasás rögös útján.

Na de miért is jó nekünk?

Br00TCn

Próbáltunk már komolyabb applikációt írni JS-ben, pl. ES5 alatt? Hány perc után fogott el a sírógörcs? Persze levetkőzhetjük a logikát, hogy backendesek szívjanak (viva la vékonykliens), de ha az éppen node.js, akkor bizony ígyis-úgyis szembesülnünk kell a ténnyel, hogy szükségünk van némi segítségre, hogy átlássuk a dolgokat. Bevezethetünk module loadereket, szétdarabolhatjuk az applikációt, viszont akkor a sok kis apró elemet lesz nehéz fejben összekötni, komolyabb változtatásokat végigvezetni azokon egy gombnyomással. Mégis hogy segíthet nekünk a típusok bevezetése? Nos az első ilyen lépés az pont ez, ugyanis ezáltal sokkal jobb kódkiegészítést kapunk, kontextusba illőt, a másik a refaktoring lehetősége, lévén az IDE végig tudja követni, hogy milyen objektumok merre járnak, adott tagváltozók nevét nem hasraütésszerűen kell kitalálnia, de erre még később visszatérünk.

Na és miért is kell? Azért, mert ez a szomorú jövő több helyen is hasznát vesszük. Az Angular2 esetében (mondjuk itt nem csak ez az egy opció) úgy mint az Ionic 2 esetében is, de ha valakit a játékfejlesztés inkább érdekel (itt nyerek olvasókat, jeee! ), akkor ott C# mellett a TypeScript is opció, amiben scriptjeinket írhatjuk.

Na de sok volt a beszéd, inkább nézzük meg mi is ez az egész!

Ahhoz, hogy működjön a dolog, első körben szükségünk lesz a typescript npm csomagjára:

npm install -g typescript

És bumm, kész is vagyunk, mehetünk haza!

Sajnos ez nem lesz ilyen egyszerű, ezért most mindenki platformtól függően keressen valami olyan IDE-t, ami támogatja a typescriptet. WebStorm, Visual Studio, VS Code, stb. elég sok editor támogatja már. A példákban a képek a csodás VS Code-ból valóak lesznek. Emlékszünk még az ES6 syntaxra? Sokban hasonlítani fogunk arra:
class Starter {
}

Ezt mentsük le egy starter.ts fájlba, majd fordítsuk le és nézzük mi is lesz belőle!
tsc starter.ts

Alapesetben ugyanoda fogja fordítani a fájlokat, de később majd megnézzük, hogy is lehet konfigurálni a typescript compilerét.

A generált fájl a starter.js lesz:
var Starter = (function () {
 function Starter() {
 }
 return Starter;
}());

Hát nem mondanánk valami szépnek, ugye? Na de az imént típusokról volt szó, nemde? Akkor nézzük csak miről is volt szó!
class Starter {
  private startingNumber : number;
 
  public constructor(startingNumber : number) {
    this.startingNumber = startingNumber;
  }
}

Hirtelen felvettünk pár plusz elemet a dologba. Először is hozzáadtunk az osztályunkhoz egy privát fieldet, ami number típusú, létrehoztunk egy konstruktort, ami ezt beállítja. Nézzünk mi is lesz ebből a generált kódban:
var Starter = (function () {
 function Starter(startingNumber) {
 this.startingNumber = startingNumber;
 }
 return Starter;
}());

Ööö.. hol a privát field, hol vannak a típusok? Nos a helyzet az, hogy a javascriptben nincsenek a access modifierek, ennélfogva azokat nem lehet ábrázolni ott. Tehát ott nem fognak megjelenni. Típusok sincsenek, tehát azok se jelennek meg.

Akkor mégis mi értelme ennek?

Selection_026

Próbáljuk meg pl. stringet átadva a konstruktornak példányosítani az osztályunkat:
starter.ts(8,25): error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.

Hát igen, a compiler bizony beszól, hogy nettó f*szság amit épp tenni próbálunk (ennek ellenére persze lefordítja az egészet...), valamint az IDE is észreveszi, hogy valami nem kóser. Akkor most nézzük az access modifiereket! A startingNumber ugye privát, ennélfogva nem kéne kívülről elérni, nemde?Selection_027

Hát nem kell félteni a rendszert, ezért is ugyanúgy beszól. Hmm, így talán lehet normális rendszereket tervezni? Azt már látjuk, hogy az access modifiereket figyelembe veszi a rendszer, valamint lehet classokat deklarálni.

Mi az helyzet az interfészekkel?

Na ez egy kicsit más tészta, mint amit más nyelvekben láthattunk, ugyanis az interfészeket nem kell implicit implementálnunk, elég ha a belső szerkezete hasonló:
interface Rucsok {
 hoursSlacked : number;
}

class Starter { // kicsit átformáltuk a dolgokat

 private rucsok : Rucsok;
 public constructor(rucsok : Rucsok) {
 this.rucsok = rucsok;
 }
}

var rucsok = {hoursSlacked : 6}; // anonym object, de ugyanazokkal a fieldekkel rendelkezik, mint a Rucsok interfész, ennélfogva kompatibilis


var start = new Starter(rucsok);

A fentiekben kissé átírtuk a dolgokat, létrehoztunk egy Rücsökinterfészt, aminek lett egy number típusú fieldje. Ezt várja konstruktorban a Starter. Mi azonban csak egy szimpla anonym objectet adunk át, aminek a fieldjei ugyanazok, így mégis megeszi azt.

Ha lefordítjuk, láthatjuk, hogy az interfészünk sehol sem szerepel, mert ez csak a typescriptben létezik, valamint a funkciója is némileg eltér más nyelvekben alkalmazott interfészektől.

Na de ha már típusok, milyen típusokat ismerünk?

boolean:
let b : boolean = true;

Hé, álljunk csak meg egy szóra! Mi ez a let? hát nem a var kulcsszóval tudunk változót deklarálni? De igen, azonban a var scopingja nem az igazi, ennélfogva ha ún. block scopingot szeretnénk a változónknak, akkor a let kulcsszóval érdemes azt tenni. Később még kitérünk a kettő közti különbségre.

number:
let workHours : number = 8;

A number, ugyanúgy ahogy a sima JavaScriptben, floating point, annyi, hogy a sima decimális és hexa forma mellett oktális és bináris formában is megadhatjuk azt.

string:
let joker : string = "Why so serious?";

Ez még semmi extra, viszont bejöttek az ún. template stringek, amik az eddigi összefűzögetést hivatottak kiküszöbölni. Ezeket nem simple/duble quote-al tudjuk meghatározni, hanem az ún. backquote-al (`). Bennük változókat is meghatározhatunk, mégpedig a ${ expression } szintaxissal. A másik plusz, hogy több soron átívelhetnek, ellenben az eddigi string literálokkal, amiket soronként le kellett zárni és + jelekkel összefűzni.

template string:



let quote : string = `- ${ joker },
  let's put a smile on this face!`;

Eddig még semmi varázslat nem történt, nézzünk a tömbökkel mi a helyzet?

array:

Na most hasonlóan pl. a C#-hoz, tömböt a []-val fogjuk jelölni, ígye:
let fibonacci : number[] = [0, 1, 1, 2, 3, 5, 8];

A másik megadási mód már sokkal fancybbnek hat, mégpedig generikus Array objektumként:
let fibonacci : Array<number> = [0,1,1,2,3,5,8];

Bizony, lesznek itt generikusok is, de erre majd még visszatérünk!

tuple:

Na ez miféle csodabogár?

Ebben az esetben egy olyan tömböt szeretnénk deklarálni, amiben bizonyos indexekhez fix típust rendelünk. Például akarunk egy string-string párost:
let entry : [string, string];

entry = ["Star", "Wars"]; // funktzioniert


entry = ["Star Wars", 7]; // csecsre fut



Amikor kikérjük az elemet az adott indexen, akkor a fordító tudni fogja, hogy milyen típust is ad vissza. Ha olyan elemet hívunk meg, amit nem határoztunk meg, akkor egyfajta uniót képez a típusok között:
let entry : [string, number];

entry[5] = "yepp"; // string, belefér


entry[5] = 0b001; // number, belefér


entry[5] = true; // csecsre fut, mert (string|number) constraintnek nem felel meg

A típus uniókba egyelőre ne menjünk bele, az egy elég advanced topic.

enum:

Hohohó, lassan már Java ez, nem is JS, nemde?

Ez egy teljesen új típus, ami nem létezik JavaScript alatt, ezért egy furcsa objektumszerkezettel írja le fordítás után:
enum Situation {BAD, WORSE, WORST}

let szitu : Situation = Situation.BAD;

Ebből a fordítás után a következő JS kód keletkezik:
var Situation;
(function (Situation) {
 Situation[Situation["BAD"] = 0] = "BAD";
 Situation[Situation["WORSE"] = 1] = "WORSE";
 Situation[Situation["WORST"] = 2] = "WORST";
})(Situation || (Situation = {}));
var szitu = Situation.BAD;

Azért így történik, mert nem csak a kulcs alapján, de index alapján is elérjük az értékeit egy enumnak. Alapesetben a hozzárendelt értékek 0-tól indexeklődnek, de mindezt felülbírálhatjuk:
enum Situation {BAD = 42, WORSE = 75, WORST = 88 }

A fentiek főleg ott használhatóak, ahol már meglévő TypeScript kóddal lépünk kapcsolatba, azonban a legtöbb esetben sima JS third party library-kat és hasonlókat is használunk, amikhez nincs ún. definition fájl (majd erről is beszélünk) és ennélfogva az ott szereplő típusokról gőzünk sincs. Ekkor jön képbe az ún. any típus.

Ezt akkor használjuk, amikor az adott változó típusellenőrzését a compiler figyelmen kívül hagyja:
let someJQueryStuff : any = $("#whatever");
someJQueryStuff = 36;
someJQueryStuff = "how about no?";

Jól látható, hogy bármit is rendelünk hozzá, nem fog beszólni a compiler. Itt ránkbízza a dolgot a compiler, tehát észnél kell lenni, hogy mi is történik. Tömbökre is alkalmazható:
let couldBeAnything : any[] = [5, "string", false];

Akkor most jöjjön, ami pont az any ellenkezője, a void "típus":

TheVoid

Amikor egy metódusunk nem tér vissza semmivel, akkor alkalmazzuk a void típust. Ezt nem lehet változóhoz rendelni, hanem függvény visszatérési értéknek:
function wontReturnAnything() : void {
  return;
}

Ha megpróbáljuk mégis megerőszakolni a dolgot, akkor biza hibát dob:Selection_028

Vannak még más speciális típusok ha visszatérési értékekről van szó. Az egyik ilyen pl. a never lesz. Ez a VS Code által használt TypeScriptben még nem lesz benne, ezért felül kell csapni a használt SDK-t. A never típust akkor használjuk, amikor a metódusunk vége unreachable. Ez nem egy gyakori példa, ezért vegyük át az eseteket:

Az egyik ilyen, mikor kivételt dobunk:
function wontReturnEver() : never {
  throw new Error("Oops");
}

A másik eset a végtelen ciklus:
function wontReturnEver() : never {
 while(true) {
   // how cool is that?

 }
}

Rafkós a rendszer, mert nem lehet könnyen átverni:

Selection_030

A harmadik eset pedig amikor ezen metódusokat hívjuk és ezek visszatérési értékét adjuk vissza:
function noShitSherlock() : never {
 return wontReturnEver();
}

Egyre "jobb" programozási gyakorlatokat láthatunk, nem? Azért nem kell feltétlenül végtelen ciklusokat használni, hogy szerepelhessen a kódunkban a never, oké? :)

A voidhoz hasonlóan akad még két másik típus, ami önmagában nem valami hasznos, az undefined és a null.

Alapesetben a null és az undefined altípusai az összes más típusnak, ennélfogva null-t vagy undefined-ot hozzárendelhetünk pl. egy stringhez vagy array-hez akár.
let test : number[] = null;
let test2 : number[] = undefined;

Ez emlékeztethet minket az erősen típusos nyelveknél megszokottakra, ugye? Jó kis null checkek a metódusok elején, netán Optional osztályok. Később majd még lesz szó az ún. strictNullChecks flagről.

Type cast

A név kissé becsapós, mert itt semmi runtime ráhatása nem lesz a dolognak, csak a compiler fogja végezni a dolgot. Ezzel tudjuk elérni, hogy a TypeScriptre ráerőszakoljuk, hogy ez a változó bizony ez a típus. Na de nézzük a szintaktikáját a dolognak:

Vegyünk egy alapesetet, hogy meghákoljuk a polimorfizmust:
export class Rucsok{
 public randomNumber : number;
}

let obj : Object = new Rucsok();

Ezután ha megpróbálunk valamit csinálni az obj változón, akkor bizony nem látunk semmit a Rucsok classból, pedig annak van egy publikus tagváltozója, ejnye!

Selection_033

 

Na akkor most vessük be a type assertion-t!
let rnd : number = (obj as Rucsok).randomNumber; // ez az as kulcsszóval működő 


rnd = (<Rucsok>obj).randomNumber; // ez pedig a kifordított generikus módszer

Ismét kihangsúlyoznám, hogy ennek semmi runtime hatása nem lesz, a generált kódban:
var rnd = obj.randomNumber;
rnd = obj.randomNumber;

nem szerepel majd a dolog, csak compile time segít.

Modulok


Sajna arra nincs elég hely itt, hogy végignyálazzuk a TypeScript összes újdonságát, ezért most fókuszáljunk inkább azokra a részekre, amik minden esetben előjönnek majd a vele való munka során. Az egyik ilyen a modulok kérdése lesz majd. Az ES2015-ben már vannak modulok, ezt pedig a TypeScript is hozza magával. Az egyik legfontosabb tudnivaló, hogy ezek a modulok saját scope-al rendelkeznek, ennélfogva nem fogják beszennyezni a globális scope-ot, kivéve ha exporttal kiexportáljuk azt és importtal pedig hivatkozunk rá. A többit kívülről nem érjük el, ellenben a kiexportált osztályban, stb. hozzáférünk a modul elemeire, amiből kihúztuk azt. Ezt fogjuk fel amolyan package private-nek (Java-sok szeme most felcsillan ).
Exportot alkalmazhatunk bármilyen deklarációra, classra, aliasra, interfészre, változóra

Ez az import nem ugyanaz, mint amit a module loaderek végeznek. A module loaderek felelősek hogy runtime behúzzák a függőségeket mielőtt futtatják a kódot. Node.JS-ben ilyen a CommonJS és weben pedig a require.js.

A TypeScript többféle module loaderhez képes kompatibilis kódot előállítani, így commonjs, amd, system, ES6, ES2015, umd-re. Na de mi is ez az egész?

Kicsit rendszerezzük a projektünket, hozzunk létre egy src mappát és egy dist mappát is. Előbbi fogja tartalmazni a ts, utóbbi a js fájlokat. A projektünk gyökerében pedig hozzunk létre egy tsconfig.json-t. Ez tartalmazza majd a konfigurációt ami szerint a compiler dolgozik majd.

A konfig fájl tartalma legyen a következő:
{
 "include": [
 "src/**/*"
 ],
 "compilerOptions": {
 "outDir": "dist",
 "module": "amd"
 }
}

Itt beállítjuk, hogy a generált fájlokat a dist mappába fogja tenni, AMD szerint hozza létre őket és az src mappából rekurzívan minden fájlt behúz. Hozzunk létre az src mappában egy some-module.ts-t:
import {AnotherModule} from "./another-module";

export class SomeModule {
 private property : AnotherModule;

 public constructor(dependency : AnotherModule) {
 this.property = dependency;
 }
}

Valamint az általa hivatkozott another-module.ts-t:
export enum AnotherModule {
 NOTHING, HERE
}

Végül az őket használó main.ts-t:
import {AnotherModule} from "./another-module";
import {SomeModule} from "./some-module";

var mod : SomeModule = new SomeModule(AnotherModule.NOTHING);

Ezután nézzük mit sikerült a rendszernek forgatnia belőle!

some-module.js
define(["require", "exports"], function (require, exports) {
 "use strict";
 var SomeModule = (function () {
 function SomeModule(dependency) {
 this.property = dependency;
 }
 return SomeModule;
 }());
 exports.SomeModule = SomeModule;
});

Ebben a modulban ugyan függünk az another-module-tól, azonban itt mégsem jelenik meg, az mert nem használjuk azt direktben, ezért nincs szükség rá. Jól látható, hogy define blockokba csomagolta a tartalmat a compiler és a require/exports modulokat default beleinjektálja. Ez azért fontos, hogy más modulokat is be tudjunk húzni vagy épp a miénket exportálni.

another-module.js

define(["require", "exports"], function (require, exports) {
 "use strict";
 (function (AnotherModule) {
 AnotherModule[AnotherModule["NOTHING"] = 0] = "NOTHING";
 AnotherModule[AnotherModule["HERE"] = 1] = "HERE";
 })(exports.AnotherModule || (exports.AnotherModule = {}));
 var AnotherModule = exports.AnotherModule;
});

Itt se jelenik meg semmilyen másik modul, lévén itt nem is használunk mást, csak kiexportáljuk az enumot amit deklaráltunk.

main.js
define(["require", "exports", "./another-module", "./some-module"], function (require, exports, another_module_1, some_module_1) {
 "use strict";
 var mod = new some_module_1.SomeModule(another_module_1.AnotherModule.NOTHING);
});

Na itt már látszik, hogy valóban használjuk a két létrehozott modult. Aliasokat képez a compiler és azokat használva tudjuk behúzni őket. Na de miért kell még az aliason belül kulcsokat is képezni? Nos részben azért, mert egy ilyen modulból több elemet is kiexportálhatok és be is importálhatok több elemet. Az importok során a beimportálandó elem neve meg kell egyezzen a kiexportált elem nevével, DE! aliasokat alkalmazhatunk, ahogy azt más nyelveknél már megszoktuk. Tehát:
import {SomeModule as some} from "./some-module";
import {AnotherModule as dep} from "./another-module";

let stuff : some = new some(dep.HERE);

Láthatjuk, hogy aliassal más néven tudjuk használni a beimportált elemeket, ha úgy tartja kedvünk.

Ha nem csak egy elemet akarunk, akkor a *-al behúzhatjuk a modulban exportált összes deklarációtegy alias alá. Ilyenkor az alias alatti kulcsokkal férünk hozzá az egyes elemekhez:
import * as some from "./some-module";
import * as dep from "./another-module";
let stuff : some.SomeModule = new some.SomeModule(dep.AnotherModule.HERE);

 
Na de mi a helyzet akkor, amikor egy third party libet használnék, ami sima JavaScript?

Ahhoz, hogy ezeket használni tudjuk, a TypeScript számára le kell írjuk annak a szerkezetét, a publikus API-ját, amit használni akarunk. Szerencsénkre elég sok ismert libhez készültek már ilyen leírók, amit .d.ts kiterjesztésű fájlokban írunk le. Hasonlóan működnek, mint a C/C++-ból is ismert header fájlok. Az ilyen fájlokat "ambient" deklarációknak nevezzük, mert nem tartalmazzák az implementációt.

Nézzünk egy példát rá!

Node.js-ben a legtöbb funkció használatához be kell importálnunk az adott modult. Rengeteg ilyen van, ezért ahelyett, hogy mindnek létrehoznánk a saját kis leíróját, inkább egy nagyba gyúrjuk azt össze.
declare module "url" {
 export interface Url {
 protocol?: string;
 hostname?: string;
 pathname?: string;
 } 
 export function parse(urlStr: string, parseQueryString?, slashesDenoteHost?): Url; 
} 
declare module "path" {
 export function normalize(p: string): string;
 export function join(...paths: any[]): string;
 export var sep: string; 
}

Ezt mentsük le egy node.d.ts fájlba. Természetesen ez csak egy töredéke a node.js API-jának, pusztán a példa kedvéért.

Ezután már tudunk rá hivatkozni az ún. triple slash használatával:
/// <reference path="node.d.ts" />

import * as URL from "url";
let myUrl = URL.parse("http://www.typescriptlang.org");

A definition fájl behúzása után ugyanúgy működik, mint egy TypeScript fájl.

Szerencsénkre nagyon sok libhez már van kész definition fájl, így nem kell azt megírnunk magunknak. Többet erről itt.

Órákon át lehetne pötyögni a TypeScriptről, de nem az a cél, csak egy kis betekintés, mielőtt beleugrunk az Angular 2-be TypeScript alapokon! Tehát egyelőre legyen elég ennyi, ha pedig bármi észrevétel van, a lenti komment szekció bárki rendelkezésére áll!

Hozzászólások betöltése
2014-2017 © Letscode.hu. Minden jog fenntartva. Build verzió: 1.2.37

Mik azok a sütik?


As is common practice with almost all professional websites this site uses cookies, which are tiny files that are downloaded to your computer, to improve your experience. This page describes what information they gather, how we use it and why we sometimes need to store these cookies. We will also share how you can prevent these cookies from being stored however this may downgrade or 'break' certain elements of the sites functionality.

Áltálnos információkat nyújthat ez a Wikipedia cikk a HTTP sütikről...

Hogy használjuk a sütiket?


We use cookies for a variety of reasons detailed below. Unfortunately is most cases there are no industry standard options for disabling cookies without completely disabling the functionality and features they add to this site. It is recommended that you leave on all cookies if you are not sure whether you need them or not in case they are used to provide a service that you use.

Sütik kikapcsolása


You can prevent the setting of cookies by adjusting the settings on your browser (see your browser Help for how to do this). Be aware that disabling cookies will affect the functionality of this and many other websites that you visit. Disabling cookies will usually result in also disabling certain functionality and features of the this site. Therefore it is recommended that you do not disable cookies.

A sütik amiket mi használunk


This site offers newsletter or email subscription services and cookies may be used to remember if you are already registered and whether to show certain notifications which might only be valid to subscribed/unsubscribed users.

In order to provide you with a great experience on this site we provide the functionality to set your preferences for how this site runs when you use it. In order to remember your preferences we need to set cookies so that this information can be called whenever you interact with a page is affected by your preferences.


Harmadik féltől származó sütik


In some special cases we also use cookies provided by trusted third parties. The following section details which third party cookies you might encounter through this site.

This site uses Google Analytics which is one of the most widespread and trusted analytics solution on the web for helping us to understand how you use the site and ways that we can improve your experience. These cookies may track things such as how long you spend on the site and the pages that you visit so we can continue to produce engaging content.

For more information on Google Analytics cookies, see the official Google Analytics page.

The Google AdSense service we use to serve advertising uses a DoubleClick cookie to serve more relevant ads across the web and limit the number of times that a given ad is shown to you.

For more information on Google AdSense see the official Google AdSense privacy FAQ.

We also use social media buttons and/or plugins on this site that allow you to connect with your social network in various ways. For these to work the following social media sites including; Facebook, will set cookies through our site which may be used to enhance your profile on their site or contribute to the data they hold for various purposes outlined in their respective privacy policies.


More Information


Hopefully that has clarified things for you and as was previously mentioned if there is something that you aren't sure whether you need or not it's usually safer to leave cookies enabled in case it does interact with one of the features you use on our site. However if you are still looking for more information then you can contact us through one of our preferred contact methods.

Email: fejlesztes@letscode.hu

Bezárás