TypeScript’i Derinlemesine İnceleme
Herkese selamlar;
Günümüzde web uygulamalarında javacriptin yeri azımsanmayacak kadar büyüktür. Javacript strongly-typed bir dil değildir. Doğal olarak kapsamlı ve büyük projelerde işler çok karmaşıklaşmaktadır. Class, Interface ve Modüller gibi birçok kavramın kullanılamaması bunun enbüyük nedenlerinden bazılarıdır.
TypeScript Visual Studio geliştirme ortamında kullanılabilmesi, compile zamanında strong-type olarak yani class, interface, module gibi tipler oluşturulabilmesi, runtime’da klasik bir Javascript gibi çalışıyor olması ve Intellisense özelliği ile kodlama kolaylığı sağlaması ençok tercih edilen javascript frameworklerinden biri olmasını sağlamaktadır. Aklıma gelmişken refactoring TypeScript için gerçekten çok başarılıdır.
Öncelikle Add NewItem TypeScriptFile deyip .ts uzantılı dosyamızı projemize aşşağıdaki ekranda görüldüğü gibi ekliyoruz.
Şimdi Örneğimize geçelim.Eklediğimiz operation.ts dosyasına aşşağıdaki kodları ekleyelim.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
interface Control<T> { text: string; value:T; } interface Rules { MaxLengtg?: number; isMultiline:boolean; } interface TextBox extends Control<number> { Font:string;Size?:number; } var txt = <TextBox>{}; txt.text = "Bora"; txt.value = 5; txt.Font = "asd"; txt.Size = null; console.log('Text:' + txt.text + ',' + 'Value:' + txt.value + ',' + 'Font:' + txt.Font + ',Size:' + txt.Size); interface TextArea extends TextBox, Rules{ } var txtArea = <TextArea>{text:"MyTextArea",value : 13,Size:5,isMultiline:true,Font:"Arial"}; //Hatalıvar txtArea = <TextArea>{ text: "MyTextArea", value: 13, Size: 5, isMultiline: true }; if (txtArea.isMultiline) { console.log(txtArea.Size + ',' + txt.text + ',' + txt.value + ','+txt.Font); } |
Yukarıda görüldüğü gibi öncelikle iki tane interface yarattık. Bunu javascriptte yapıyoruz:) Control interfaceimizde generic tip tanımlanmaktadır. Textbox interface’ini Control’den türettik. Extends komutu bu işe yaramaktadır. Ayrıca Textbox interface’ine bazı propertyler ekledik. TextArea interface’ini ilk yaratırken Font property’sine değer atmaz isek hata oluşur. Hata oluşmaması için TextArea interface’nin türediği, TextBox interface’inde Font property’si aşşağıdaki gibi eklenmelidir. Çünkü required dır.
1 |
interface TextBox extends Control<number> { Font?:string;Size?:number; } |
Ayrıca TextArea interface’i Rule interfaceinden türetilmiştir. Ve isMultiline dönüş tipine göre console yazılmaktadır.
Görüldüğü üzere TypeScript ile aynı strong-type dil gibi çalışılmaktadır.
Uygulama çalıştırılınca
operation.ts==>operation.js’e
dönüştürülür.
Dönüştürülen kod aşşağıdaki gibidir.(operation.js):
1 2 3 4 5 6 7 8 9 10 11 12 13 |
var txt = {}; txt.text = "Bora"; txt.value = 5; txt.Font = "asd"; txt.Size = null; console.log('Text:' + txt.text + ',' + 'Value:' + txt.value + ',' + 'Font:' + txt.Font + ',Size:' + txt.Size); var txtArea = { text: "MyTextArea", value: 13, Size: 5, isMultiline: true, Font: "Arial" }; //Hatalıvar txtArea = <TextArea>{ text: "MyTextArea", value: 13, Size: 5, isMultiline: true }; if (txtArea.isMultiline) { console.log(txtArea.Size + ',' + txt.text + ',' + txt.value + ',' + txt.Font); } |
Şimdi biraz da classlarla uğraşalım.
1 2 3 4 5 6 7 8 9 10 11 |
class CustomControl implements Control<number> { text: string; value: number; constructor(t: string, v: number) { this.text = t; this.value = v; console.log(t + '-' + v);} cutText(len: number) { console.log(this.text.substring(0, len)); } } var cnt: Control<number>; cnt = new CustomControl("txtBora", 36); console.log(cnt.text); console.log(cnt.value); |
Yukarıda görüldüğü gibi CustomControl‘umuzu Control‘den türettik. Ve constructor’ında property değerlerini atadık. Dikkat ederseniz cnt.cutText’e erişememekteyiz. Erişebilmek için aşşağıda görüldüğü gibi bir değişiklik yapmalıyız. Böylece CustomControl methodlarına erişebiliriz.
1 2 |
var cnt = new CustomControl("txtBora", 36); cnt.cutText(3); |
Şimdi aşşağıda görüldüğü gibi classımızdan inheritance yapalım:
1 2 3 4 5 6 7 |
class CustomControl implements Control<number> { text: string; value: number; constructor(t: string, v: number) { this.text = t; this.value = v; console.log(t + '-' + v); } cutText(len: number) { console.log("CustomControl :"+this.text.substring(0, len)); return this.text.substring(0, len); } public getText(): string { return this.text;} } |
1 2 3 4 5 6 7 8 9 10 11 12 |
class BoraControl extends CustomControl { private bkName: string; constructor(name: string, age: number, bikeName: string) { super(name, age); this.bkName = bikeName; } Go() { this.text = this.cutText(2); console.log("BoraControl :"+this.bkName + " ile gidiyor."); console.log("BoraControl :" +"Esas Name=" + super.getText()); console.log("BoraControl :" +"Değişen Name=" + this.text); } } var cnt2 = new BoraControl("Duru", 2, "Bisiklet"); cnt2.Go(); |
Yukardaki kod üzerinde biraz konuşalım.BoraControl CustomContro’den türemiştir(extends).Constructor’ında (super) komutu ile base anlamına gelen yaratıldığı classın constructor parametrelerini göndermiştir.
private erişimli bkName parametresine text değeri atılmıştır.Böylece cnt2. denince bu değişkene erişilemez.
Ve kendine özel Go() methodu çağrılmıştır.Bu Go() methodunda atanan text’i değiştirmiştir(cutText(2) methodu ile).
Ayrıca base yani super’daki text değirine dışardan erişmek için public bir method’a ihtiyaç vardır.Örnek kod parçacığı aşşağıda gösterilmektedir.
1 |
public getText(): string { return this.text;} |
Sizce burda çıktı nasıl olur?
Yani Türetilen sınıfın property’si değişti.Türeten sınıfın ve türetilen sınıfın yani BoraControl ve CustomControl’ün text’i ne olur?
Görüldüğü üzere iki side değişir.Çünkü referans değerleri ile birbirlerine bağlıdırlar.
Kodumuzu aşşağıdaki gibi değiştirir isek:
1 2 3 4 5 6 7 8 9 10 11 12 |
class BoraControl extends CustomControl { private bkName: string; constructor(name: string, age: number, bikeName: string) { super(name, age); this.bkName = bikeName; } Go() { this.bkName = this.cutText(2); console.log("BoraControl :"+this.bkName + " ile gidiyor."); console.log("BoraControl :" +"Esas Name=" + super.getText()); console.log("BoraControl :" +"Değişen Name=" + this.bkName); } } var cnt2 = new BoraControl("Duru", 2, "Bisiklet"); cnt2.Go(); |
Bu durumda herhangi bir hata ile karşılaşmayız. Çünkü BoraControl’ü ait private bkName değiştirmemiz base class’ı ilgilendirmez.
Biraz da static yapılar ve enumlar üzerinde konuşalım:
Örneğimizde yapılan aktiviteler ile toplam yakılan kaloriyi yazdırıcaz.
Cordinat‘diye bir static sınıf yaratalım. Bu gidilecek yerlerin x,y şeklinde kordinatları olsun. Bir nevi enum gibi çalıştırıcaz.
Activite diye enum’umuz var.Value değerleri km’de yakılan kalori miktarını tutsun[Run,Walk… gibi].
RunKeeper classımız var.Bu aslında bizim esas işlemi yapacak tool’umuz. Static home parametresi [x,y] değerlerini içeriyor. Başlangıç noktamız.Ve erişimine dikkat eder isek =>
Static yapıya erişim RunKeeper.home.x veya RunKeeper.home.y
private kmCalori sınıfa ait km’de yakılacak kalori tutulur.constructor’da constructor(calorie: number= 5) eğer parametre null ise default 5 değeri atanmıştır.
getTotalRunDistance() verilen kordinatlar ile home’a olan mesafeye göre toplam kat edilen yol km cinsinden hesaplanır.
3. Farkıl kullanım şekli tanımları aşşağıdaki gibidir.Hepsi de sonuçta yakılan toplam kaloriyi yazdırır.Örnek kod aşşağıdadır.
- getTotalCaloriCordinate() direk kordinatlar girilerek çağrılmıştır.RunKeeper() yeni yaratılırken constructor’a parametre geçilmediği için kmCalorie default olan 5 değerini almıştır runTool.getTotalCaloriCordinate(20, 25)); ile de toplam katedilen mesafe getTotalRunDistance() ile hesaplanıp, km’de yakılan kalori ile çarpılıp sonuca gidilmiştir.
- getTotalCaloriPlace() points girilerek çağrılıyor.Points’de Cordinate static classındaki point verilerek katedilen mesafe getTotalRunDistance() ile hesaplanmıştır. runTool.getTotalCaloriPlace(Cordinate.Park.point)); ile de toplam katedilen mesafe km’de yakılan kalori çarpılıp sonuca gidilmiştir.
- getTotalCaloriActivite()’miz enum olan Activite’den, km’de yakılan kalori değerini bekliyor hem de yine point değeri dönen Static Cordinate ‘den kat edilen yol getTotalRunDistance() ile hesaplanıyor. runTool.getTotalCaloriActivite(Activite.RideMotorCycle, Cordinate.Job.point)) ile de toplam katedilen mesafe km’de yakılan kalori çarpılıp sonuca gidilmiştir.
Bu üç örnekle static ve enum objeleri nasıl kullanabileceğimizi aşşağıdaki örnekte olduğu gördük.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
class points { x: number; y: number; } class Cordinate { public static Market = { point: { x: 20, y: 25 } }; public static Park = { point: { x: 25, y: 30 } }; public static Job = { point: { x: 30, y: 35 } }; } enum Activite { Run=10, Walk=5, RideMotorCycle=6 } class RunKeeper { static home = { x: 15, y: 20 }; private kmCalorie; constructor(calorie: number= 5) { this.kmCalorie = calorie; } private getTotalRunDistance(points: { x: number; y: number }): number { var xDist = points.x - RunKeeper.home.x; var yDist = points.y - RunKeeper.home.y; return xDist + yDist; } getTotalCaloriCordinate(finishX: number, finishY: number): number { return this.getTotalRunDistance({ x: finishX, y: finishY }) * this.kmCalorie; } getTotalCaloriPlace(place: points): number { return this.getTotalRunDistance({ x: place.x, y: place.y }) * this.kmCalorie; } getTotalCaloriActivite(totalCalori: Activite, place:points): number { return this.getTotalRunDistance({ x: place.x, y: place.y }) * totalCalori; } } var runTool = new RunKeeper(); console.log("Total Calorie Points: " + runTool.getTotalCaloriCordinate(20, 25)); console.log("Total Calorie Cordinate: " + runTool.getTotalCaloriPlace(Cordinate.Park.point)); console.log("Total Calorie Activite: " + runTool.getTotalCaloriActivite(Activite.RideMotorCycle, Cordinate.Job.point)); |
Örnek Ekran çıktısı aşşağıdaki gibidir:
Bir de bu işlemi Module’lere bölüp yapalım:
Adından belli olduğu üzere işlemlerimizi farklı .ts dosyalara bölücez:Projemizde \Scripts\Modules altına .ts dosyalarımızı yaratıyoruz.
Tek Tek dosyalarımıza bakalım:
Points.ts:
1 2 3 |
module Operate { export class points { x: number; y: number; } } |
module Operate altına, tüm kullanılacak dosyaları aşşağıda görüldüğü gibi eklenmiştir. Bu eklenen class ve enumların başına export tagı konulmuştur. Amaç, dağıtık uygulamalarda dışardan erişimi sağlamaktır.
RunKeeper.ts:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
module Operate { export class RunKeeper { static home = { x: 15, y: 20 }; public kmCalorie; constructor(calorie: number= 5) { this.kmCalorie = calorie; } public getTotalRunDistance(points: { x: number; y: number }): number { var xDist = points.x - RunKeeper.home.x; var yDist = points.y - RunKeeper.home.y; return xDist + yDist; } } } |
Burada işlem önceliği çok önemlidir. Yukarıda görüldüğü gibi önce ortak kullanılan classlarımızı yarattık. Bunlar diğer classlarımızda kullanılıcak olan operational classlardır. Bir de dikkat ederseniz private olarak tanımlanan değişkenler ortak kullanımda artık public olarak tanımlanmışlardır. Amaç diğer classlardan da erişim sağlanmasıdır.
Coridinate.ts:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
module Operate { var runKeeper = new Operate.RunKeeper(); export class Map { public static Market = { point: { x: 20, y: 25 } }; public static Park = { point: { x: 25, y: 30 } }; public static Job = { point: { x: 30, y: 35 } }; } export class Cordinate { getTotalCaloriCordinate(finishX: number, finishY: number): number { return runKeeper.getTotalRunDistance({ x: finishX, y: finishY }) * runKeeper.kmCalorie; } } } |
Yukarıda görüldüğü gibi dikkat ederseniz var runKeeper=new Operate.RunKeeper(); olarak yaratılıp kullanılmıştır. Ayrıca RunKeeper’da kmCalorie public olmasa idi runKeeper.kmCalorie şeklinde erişemezdik.
İki class birden burada aşşağıdaki gibi tanımlanmıştır.
Place.ts:
1 2 3 4 5 6 7 8 |
module Operate { var runKeeper = new Operate.RunKeeper(); export class Place { getTotalCaloriPlace(place: points): number { return runKeeper.getTotalRunDistance({ x: place.x, y: place.y }) * runKeeper.kmCalorie; } } } |
Activate.ts:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
module Operate { var runKeeper = new Operate.RunKeeper(); export enum Activite { Run= 10, Walk= 5, RideMotorCycle= 6 } export class Activate { getTotalCaloriActivite(totalCalori: Activite, place: points): number { return runKeeper.getTotalRunDistance({ x: place.x, y: place.y }) * totalCalori; } } } |
Gene burada enum’da tanımlanmıştır.
Hepsinin kullanıldığı esas işlemin yapıldığı ts aşşağıda görüldüğü gibidir.
global.ts:
1 2 3 4 5 6 7 8 9 10 |
/// <reference path="Activate.ts" /> /// <reference path="Cordinate.ts" /> /// <reference path="Place.ts" /> var runCordinate = new Operate.Cordinate; var runPlace = new Operate.Place; var runActivate = new Operate.Activate; console.log("Total Calorie Points: " + runCordinate.getTotalCaloriCordinate(20, 25)); console.log("Total Calorie Cordinate: " + runPlace.getTotalCaloriPlace(Operate.Map.Park.point)); console.log("Total Calorie Activite: " + runActivate.getTotalCaloriActivite(Operate.Activite.RideMotorCycle, Operate.Map.Job.point)); |
Burda dikkat edilmesi gereken nokta diğer kullanılacak .ts dosyalar reference path şeklinde import edildi. Ve kullanılacak sınıflar tek tek yaratıldı.
Gelelim bu .ts konucağa sayfaya.Ben Yeni bir Index2 view’ı yarattım. Burada ts dosyaları eklemenin birkaç yolu vardır.
1.Tüm ts’leri tekbir paket altında toplayan console komutu.
tsc –out global.js global.ts
2.Bütün .js dosyalarını sayfaya doğrı sıra ile tanıtmak. Hiyerarşi çok önemlidir.Örnek kod aşşğıda görüldüğü gibidir.
1 2 3 4 5 6 7 8 9 10 11 |
@{ ViewBag.Title = "Index2"; } <script src="~/Scripts/Modules/Point.js"></script> <script src="~/Scripts/Modules/RunKeeper.js"></script> <script src="~/Scripts/Modules/Activate.js"></script> <script src="~/Scripts/Modules/Cordinate.js"></script> <script src="~/Scripts/Modules/Place.js"></script> <script src="~/Scripts/Modules/global.js"></script> <h2>Index2</h2> |
Ekran Çıktısı aşşağıdaki gibidir.
Görüldüğü üzere javascript’ini yoğun olarak kullanıldığı projelerde refactoring, kodun anlaşılabilmesi, çalışma kolaylığı, autocomplete ve strong-type dil desteğinin gücü typescript’in , ilerde çokça kullanılacağının en büyük kanıtıdır.
Geldik bir makalenin daha sonuna.
Yeni bir makalede görüşmek üzere herkese hoşçakalın.
Source Code:http://www.borakasmer.com/projects/TyepScript.rar
Son Yorumlar