TypeScript - szybki start05.XI.2014
Spis treści
- Dlaczego TypeScript?
- TypeScript vs jQuery
- Modułowość
- Podsumowując (bo poniżej są już tylko przykłady użycia)
- Instalacja
- Przykład użycia
Dlaczego TypeScript?
Siłą JavaScriptu polega na tym, że jest on dynamiczny i niczym nie ograniczony. Można za pomocą jednej linii zrealizować funkjonalności, których realizacja w C# byłaby dość uciążliwa.
Mowa tu nie tylko o wbudowanych metodach takich jak setTimeout
czy setInterval
dla których w C# należałoby stworzyć oddzielny wątek, Timer
lub Task
. Ale również o dosyć swobodnym podejściu do istnienia metod ich przepinania oraz (niestety w dużej mierze problematycznego) wskaźnika this
i co najważniejsze dość liberalnym podejściu do parametrów przekazywanych do metod.
Niestety wraz z niezwykłą elastycznością języka pojawiają się problemy, które nie są spotykane w językach silnie typowanych, np C#. Gdzie przekazywane obiekty mają (w 99,9% przypadków - przypadek dynamic
) jasno określony typ.
Dlatego jeśli nie mamy dobrego środowiska, które podpowiada nam typy obiektów przekazywanych do metod w JavaScript, a dokładniej ich pól oraz metod, sprawa staje się dość uciążliwa.
Z pomocą przychodzi TypeScript, który jest jedynie delikatym rozszerzeniem dla kodu tworzonego w JavaScript. Dodatkowo nie narzuca kompletnej zmiany składni tak jak to ma miejsce w CoffeeScript a pozwala edytorom podpowiadającym składnię na trafne sugerowanie podpowiedzi podczas tworzenia kodu - w Visual Studio 2013 działa to wyśmienicie.
Poza tym nawet jeśli edytor nie osbługuje w pełni podpowiedzi z kompilatora TypeScript to przecież sam kompilator wyświetla błędy kompilacji i na ich podstawie można łatwo zdiagnozować problem.
To nie wszystko. Dostajemy również dziedziczenie klas, którego standardowo w JavaScript nie ma i trzeba troszkę się namęczyć i uważać na pomyłki lub literówki przy tworzeniu kodu niepotrzebnie generując szablonowe kawałki kodu. Tu TypeScipt błyszczy (przykłady poniżej - wersja kodu sprzed kompilacji jest dużo bardziej czytelna i nie zawiera zbędnych "śmieci", które zaciemniają zrozumienie kodu - co nie oznacza, że są niepotrzebne).
TypeScript vs jQuery
Faktem jest, że na rynku istnieje wiele bibliotek JavaScript, które nie zostały stworzone przy użyciu TypeScript i nie posiadają silnie typowanych definicji swoich obiektów. Przykładami są właśnie jQuery czy AngularJs.
Tu z pomocą przychodzi projekt [2] rozwijany na GitHub-ie będący swojego rodzaju repozytorium definicji typów dla bibliotek, które ich w świadomy sposób nie tworzą.
Modułowość
TypeScript w bardzo prosty sposób pozwala na tworzenie modułów, które łatwo jest potem użyć do innch celów. Poniższy przykład zobrazuje sytuację.
// shapes.ts
module Shapes {
export class Polygon {
public x : number;
setX(_x : number) {
this.x = _x;
}
public isSquare() {
return false;
}
}
}
// main.ts
class Square extends Shapes.Polygon {
constructor(dim : number) {
super();
this.setX(dim);
}
public isSquare() {
return true;
}
}
var s : Shapes.Polygon = new Square(40);
console.log(s.x, s.isSquare());
// wynik: 40 true
Kompilacja i uruchomienie testowe:
\> tsc --out o.js shapes.ts main.ts && node o.js
40 true
Ważne jest jednak aby nie zamienić kolejności plików kompilowanych przez TypeScript bo dostaniemy komunikat jak poniżej. Kompilator TypeScript dołącza do pliku wynikowego kompilowane pliki w kolejności jakiej zostały przekazane do polecenia.
...\o.js:17
})(Shapes.Polygon);
^
TypeError: Cannot read property 'Polygon' of undefined
Jeśli jednak nie chcemy podawać na raz kilku plików możemy je referować do siebie wzajemnie umieszczając odpowiednią dyrektywę w komentarzu na początku pliku.
/// <reference path="./shapes.ts"/>
\> tsc --out o.js main.ts && node o.js
40 true
Podsumowując (bo poniżej są już tylko przykłady użycia)
Według mnie wydaje się niemądrym niekorzystanie z dobrodziejstwa TypeScript. W szczególności gdy można sobie do np SublimeText napisać poniższe zadanie, które skompiluje kod TypeScript do samego JavaScriptu i uruchomi go w NodeJs aby można było od razu zobaczyć wyniki swojej pracy.
Instalacja
Instalacja nie jest specjalnie uciążliwa bo jeśli korzystamy z Visual Studio 2013 Update 2 to TypeScript jest wbudowany w to narzędzie. Jeśli chcemy kompilować samemu z linii poleceń za pomocą tsc
to należy w konsoli wpisać poniższe w konsoli.
\> npm install -g typescript
Póki co nie robimy nic specjalnego czego nie oferuje strona domowa projektu. Co więcej dokładna instrukcja jak używać języka znajduje się pod adresem [1]. Najwięcej chyba zobrazuje przykład.
Przykład użycia
interface Person {
FirstName: string;
LastName: string;
hi();
elo();
havCtx(context : string): LambdaFunc;
}
interface LambdaFunc {
(prefix : string) : string;
}
class Father {
FathersName : string;
constructor() {}
private nop() : void {
return;
}
}
class Student extends Father implements Person {
static PI : number = 3.1415;
FirstName: string;
LastName: string;
constructor(first: string, last: string, father?: string) {
super();
this.FirstName = first;
this.LastName = last;
this.FathersName = father;
this._lavaTemp = 1000;
}
public hi() {
return 'hello ' + this.FirstName + '. ';
}
public elo() : LambdaFunc {
return (what:string):string => {
return what + this.FirstName;
}
}
public havCtx(context : string): LambdaFunc{
return (what : string):string => {
return context + "_" + what + "_" + this.FirstName + "_sonOf_" + this.FathersName + ". ";
}
}
private _lavaTemp : number;
get LavaTemp() : number {
return this._lavaTemp;
}
set LavaTemp(newTemp : number) {
this._lavaTemp = newTemp + 273 + Student.PI;
}
}
interface IHybrid {
(i:number):string;
Name:string;
each(col:any);
}
var Hybrid : IHybrid;
(() => {
var fn : any = function(i : number) : string {
return "i=" + i;
}
fn.Name = "Hela";
fn.each = (col) => {
var cnt = 0;
for(var k in col) {
col[k];
cnt++;
}
return cnt;
}
Hybrid = fn;
})()
function hello(person : Person) {
return "My name is " + person.FirstName + ' ' + person.LastName + '. ';
}
var s:Student = new Student("jan", "kowalski", "jerzy");
var p:Person = s;
var stype : typeof Student = Student;
s.LavaTemp = 15;
var f:LambdaFunc = p.elo();
var f2 : LambdaFunc = p.havCtx("trololo");
var f3 : LambdaFunc = p.havCtx("ada")
console.log(hello(p), p.hi(), s.LavaTemp, Student.PI, stype.PI, f("firstName="), f2("elooo"), Hybrid(3), Hybrid.Name, Hybrid.each({a:3,b:4}));
Wynik działania:
My name is jan kowalski. hello jan. 291.1415 3.1415 3.1415 firstName=jan trololo_elooo_jan_sonOf_jerzy. i=3 Hela 2
Tak wygląda w/w kod po skompilowaniu przez tsc
(został użyty parametr --target ES5
ponieważ dopiero od wersji ECMAScript5 dostępne są akcesory pól)
\> tsc --target ES5 TypeScript.node.js
var __extends = this.__extends || function (d, b) {
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
function __() { this.constructor = d; }
__.prototype = b.prototype;
d.prototype = new __();
};
var Father = (function () {
function Father() {
}
Father.prototype.nop = function () {
return;
};
return Father;
})();
var Student = (function (_super) {
__extends(Student, _super);
function Student(first, last, father) {
_super.call(this);
this.FirstName = first;
this.LastName = last;
this.FathersName = father;
this._lavaTemp = 1000;
}
Student.prototype.hi = function () {
return 'hello ' + this.FirstName + '. ';
};
Student.prototype.elo = function () {
var _this = this;
return function (what) {
return what + _this.FirstName;
};
};
Student.prototype.havCtx = function (context) {
var _this = this;
return function (what) {
return context + "_" + what + "_" + _this.FirstName + "_sonOf_" + _this.FathersName + ". ";
};
};
Object.defineProperty(Student.prototype, "LavaTemp", {
get: function () {
return this._lavaTemp;
},
set: function (newTemp) {
this._lavaTemp = newTemp + 273 + Student.PI;
},
enumerable: true,
configurable: true
});
Student.PI = 3.1415;
return Student;
})(Father);
var Hybrid;
(function () {
var fn = function (i) {
return "i=" + i;
};
fn.Name = "Hela";
fn.each = function (col) {
var cnt = 0;
for (var k in col) {
col[k];
cnt++;
}
return cnt;
};
Hybrid = fn;
})();
function hello(person) {
return "My name is " + person.FirstName + ' ' + person.LastName + '. ';
}
var s = new Student("jan", "kowalski", "jerzy");
var p = s;
var stype = Student;
s.LavaTemp = 15;
var f = p.elo();
var f2 = p.havCtx("trololo");
var f3 = p.havCtx("ada");
console.log(hello(p), p.hi(), s.LavaTemp, Student.PI, stype.PI, f("firstName="), f2("elooo"), Hybrid(3), Hybrid.Name, Hybrid.each({ a: 3, b: 4 }));