Angular 2 ve React perspektifinden `yetecek kadar` TypeScript : 1 – Types, OOP,Decorators ( annotation)

Bir önceki yazımızda genel olarak “TypeScript nedir” ona bakmaya çalışmıştık. Bu yazımızda, typescript in her zaman kullancagimiz bazi özelliklerinin ilk kısmına bakarak devam edelim.

İlk olarak bir konuyu belirtmekte fayda var, TypeScript içndeki bir çok kavram zaten Javascript in mevcut sürümü(ES5) yada yeni sürümü (ES6) ile alakalı yani TypeScript in kendisinden kaynaklanan özellikler değil.

TypeScriptin bu kavramlar adına rolü ise henüz tarayıcılar ve diğer Javascript runt-time lar (node.js gibi) tarafından desteklenmeyen bu yeni özellikleri(eski tarayıcılarıda kapsayacak) şekilde sorunsuz olarak kullanmamıza imkan vermesi.

Diğer taraftan typescript ile gelen ve Javascript in dil yapısında olmayan özellikleri ise (interface, tip sistemi vs. gibi) ayrıyeten belirtmeye çalışacağım(TSÖ kısaltması ile). Aksi belirtilmedeği takdirde, konuştuğumuz konular Javascript in şimdiki ve gelecek sürümlerinde olan özelliklerin TypeScript ile kullanılmasından ibaret de diyebiliriz.

Başka bir önemli nokta ise, ES6 yada Es7  olarak bildiğimiz Javascriptin yeni sürümleri ile gelen özelliklerin tamamını burada ayrıca anlatmayacağım. Bu konular, blogdaki Javascript bölümünde olacağı için buarada sadece TypeScript + ES6/ES7 ile gelen ve özellikle Web uygulamarı ve Angular 2 / React /Meteor gibi framework ler için önemli olan konulara değinmeye çalışacağım.

Başka bir değiişle, Tyepscript i Angular 2 veya React için yetecek kadar ele almış olacağız. Bir çok kişi, Typescript ile bu frameworkler sebebiyle haşır neşir olmak zorunda bu yüzden “özellikle web uygulamaları ve frameworkler için `yetecek kadar` TypeScript” şeklinde tanımaya çalışacağız. Gereksiz bir çok detaydan kaçınıp “compact” bir anlatım yapmaya gayret edeceğiz.

 Types in TypeScript/  Tip Sistemi (TSÖ)

Tip sistemi dediğimiz konu aslında Typescript açısından en iyi anlamamız gereken başlıklardan biri. Javascript kendi içinde (ES5 itibariyle) çok zengin bir tip sistemine ve veri türü yelpazesine sahip değil diyebiliriz. Typescript bir kaç ekstra veri türünü kullanmamıza imkan versede(enums, tuples vs. gibi), Typescript in bu konuda sağladığı esas yetenek “yeni veri tiplerinin eklenmesi yada mevcutların güçlendirilmesi” değil diyebiliriz.

Bunun yerine, TypeScript ve tip sistemi ile ilgili anlamamız gereken önemli noktaları şu şekilde sıralaya biliriz;

  1. TypeScript in, geliştirme sürecinde , statik tip kontrolü sağlaması ve typescript compiler ın bize muhtemel hataları bulup uyarması,
  2. Daha büyük projelerde , diğer dillerden alışık olduğumuz “namespace ve klasik OOP ” imkanlarına benzer yetenekler le bir şekilde kendi tiplerimizi(daha tanıdık ve daha OO şekilde) oluşturabilmemize imkan vermesi.
  3. Interfaceler ile geliştirme süreci için ve çalışma zamanında kullanılacak kodları etkilemeyecek tanımlamalar yapıp hem kod organizasyonu hem de intellisense (editörlerin kod tamamla özelliği) gibi kolaylıklar sağlaması
  4. Statik tip desteği sayesinde, geliştirme sürecinde, daha iyi debug  ve kullandığınız editör için daha iyi bir intellisense kullanabilme imkanı sunması
  5. TypeScriptle gelen ve Javascript içinde olmayan bazı yeni veri türlerini kullanabilmemiz.(enums, tupples, void vs.)

Yukarıdaki tüm maddeler dikkat ettiyseniz, “geliştirme sürecimiz” için geçerli. Başka bir değişle, yazdığmız typescript , standard javascript e dönüştüğü zaman, yukarıdaki maddelerin hiç biri bizim için kullanılabilir olmayacak.

Angular 2 nin tamamen Typescript ile yazılması, şüphesiz typsescript in popülerleşmesinde çok etkili oldu. Bu süreçte henel olarak şöyle bir kanı oluştu “Sadece angular 2 için yeni bir dil mi öğrenmek zorunda kalacağım? “  Tyepscript yeni bir dil değil demiştik, ve aynı şekilde sadece angular 2 için kullanıma sunulmuşda değil aşağıdaki görsele bakarak devam edelim;

typescript-es6-es5

 

Yukarıdaki resimden de anlayabileceğimiz gibi, Typescript, bugünki klasik Javascript i kapsayan ve (geliştirme süreci için) bazı eklemeler ve iyileştirmeler elde ettiğimiz sanal bir Javascript sürümü diyebiliriz. (Microsoftun ve C# geliştiriclerinin Javascript i görmek istedikleri hali de diyebiliriz)

Bu yazıda önceliğimiz Angular 2 ve React olduğu için aşağıdaki başlıklarla devam edelim ;

  •  Types in TypeScript – (Statik)Tip sistemi
  • Interfaces
  • Classes
  •  Working with other libraries / Diğer kütüphanelerleri(momemet.js, d3,lodash vs.) typescirpt ile kullanmak
  • Decorators

Types in TypeScript – (Statik)Tip sistemi

Typescirpt geliştirme süreci için tip desteği sağlıyor demiştik şimdi aşağıdaki koda bakalım;

function topla(a,b){
 return a +b
}
topla(1,2) // 3
topla("a" , 6) //"a6"

yukarıdaki kod parçası klasik Javascript kodlarından oluşmakta, yukarıdaki kodu editörümde yazarken javascript in tip sistemi olmadığı için dinamik editörüm bazı tutarsız tahminler dışında bana hiç bir tavisyede yada uyarıda bulunamaycaktır.

topla() fonksiyonumuzu ilk çağırdığımızda, iki tane sayısal değer kullanmaktayız (1 ve 2 ) fakat ikinci çağırışımızda, bir “string” ile “number” türünü toplamaya çalışıyoruz.

İster kendi yazdığımız bir fonksiyon olsun isterse başkası tarafından yazılmış bir fonksiyon(yada ehernagi bir kod bloğu/parçası) olsun, geliştirme sürecinde tip desteği ekisikliğinden dolayı fonksiyon(veya ilgili kod parçası) hakkında  bilgi eksikiliği söz konusu.

Bu eksikliği aşmanın ilk yolu aşağıdaki klasik yol ;

//@parametre a tür int
//@parametre b tür int
//@return type int
function ekle(a,b){
   return a +b
}

kodlarımıza bazı yorumlar ekleyerek, hem sonradan bakacağımız zamanlar için hemde diğer geliştiriciler için bazı kolaylıklar sağlamaya çalışılabilir.  Ama sizinde tahmin edebileceğiniz gibi bu yöntem çok geçecerli bir yol olmaycaktır.Üstelik kod yazarken ilgili kısımları bulup tekrar tekrar yorumları okumak vs gibi zorlukları hep olacaktır.

Typescriptin ilk getirisi işte burada çıkmakta, kod yazarken veya başkası tarafından yazdılan kodları kullanırken, bize temel seviyede bir çok tip “contract” tnaimlama/ kullanma imkani vermekte.

Böylece hem kodu yazarken, kullanacağınız IDE yada Editörün size bir Intellisense/kod tamamlama desteği sağlamasına hem de daha kodları yazarken eğer int türü yerine bir string parametre kullanmaya çalışıyorsak  bizi zamanında uyarmaya yardımcı olması.

Şimdi yukarıdaki fonksiyonu Typescript e çevirelim;

function topla(a:number ,b:number) : number {
 return a +b
}

Fonksiyonumuza baktığımızda farklı olarak gördüğümüz (normalde Javascript içinde olmayan) iki şey var,

  1. a:number , b:number
    1. fonksiyonumuzun parametrelerine birer tip ataması yaptık. Fonksiyonumuzun iki tane (number) integer değer alacağını belirttik
  2. Fonksiyonumuz için bir return tipi de atadık
    1. function():tip {}  şeklinde , fonksiyonlarımızın döndüreceği değer hakkındada bir tanımlama yapmış olduk. Bizim örneğimiz de. fonksiyonumuz bir :number(integer) değer döndürmekte.

Şimdi tip desteğinin bize sağladığı kolaylığı biraz daha iyi anlamak adına aşağıdaki görüntüye bakalım;

 

Videodan da gördüğümüz üzere ilk kazancımız geliştirme zmanında, anlık geri dönüşler. Dikkat ettiyseniz biz her hangi bir derleme vs yapmadık, editörümün typescript desteği olduğu için (Visual Studio Code) , ben daha derleme yapmadan gerkeli hataları ve eksiklikleri bana bildirmekte. Ayrıca, fonksiyonumla ilgili genel bilgiyide(2 adet :number parametre ve bir :number değer geri dönüyor) bana sunmakta.

Artık hemen hemen her editör Typescript desteğine sahip, eğer editörünüzün tyepscript detseği yoksa, kodumuzu tsc(typsecript compiler ) ile derleyip yine hata denetimine tabi tutabilirsiniz. Şimdi , tip sistemi ile ilgili diğer bazı konulara bakalım ;

  • Tanımladığımız değişkenlere bir tip ataması yapmak için değişken isminden sonra ” : ” işaretini kullanıyoruz
    • let sayi :number  = 5
    • let metin: string = “yazılım günlüğü”
  • Eğer ” :tip ” şeklinde bir tnaımlama yapmazsak, Typescript bizim için değişkenin sahip olduğu değere bakarak bir thamin de bulunacak
    • let sayi = 5 // typescript :number olarak kabul edecek
    • let metin = “insan” // string 
  • fonksiyonlarımıza dönüşdeğeri atamak için de “fonskisyon(parametre1:tip) : tip” sentaksını kullanıyoruz.
    • parametre listesinden sonra , dönüş tipini belirtmekteyiz
    • eğer bir dönüş tipi belirtmezsek, typescript, bir tahminde bulunmaya çalışacak ama bu tahmin değişkenlerdeki gibi her zaman başarılı bir thamin olmayabiliriyor, bu yüzden dönüş değerini her zaman belirtmekte fayda var
    • Eğer bir fonksiyon her hangi bir değer döndürmeyecekse “void” anahtar kelimesini kullanıyoruz
      • function degerDonmuyor(a:number) : void { }

Şimdide Typescriptin bize sağladığı temel tip lere bakalım ;

Basic Types / Temel Tipler;

  1. Boolean
    1. true / false => let isDone: boolean = false;
  2. Number (hem int hem double degerler icin)
    1. let decimal: number = 6;
    2. let hex: number = 0xf00d;
    3. let binary: number = 0b1010;
    4. let octal: number = 0o744
  3. String
    1. let color: string = “blue”;
  4. Array (iki farkli tanimlama imkani. Asagidaki iki tanimlamada es deger)
    1. let list: number[] = [1, 2, 3];
    2. let list: Array<number> = [1, 2, 3];
  5. Tuple
    1. let x: [string, number];
    2. x = ["hello”, 10];
  6. Enum
    1. enum Color {Red, Green, Blue};
    2. let c: Color = Color.Green;
  7. Any (degiskenin hangi degeri tasiyacagindan emin degilseniz yada duruma gore farkli degerler tasiyacaksa )
    1. let notSure: any = 4;
    2. notSure = “maybe a string instead”;  // ilk olarak string atamasi her hangi bir hata olmayacak
    3. notSure = false; // sonrasinda bir boolean deger de tasiyabilir

Bu temel veri türlerimiz hemen hemen her programlama dilinde olan ve muhtemelen zaten aşina olduğunuz yapılar. Bu noktada bir küçük başlığada değinmekte fayda var , “Type assertions ” ;

Eğer Typescript derleyicisine, bir değişken için bir tip i kullanması için zorlama yapmanız gerekirse yada C#, Java gibi dillerdekine benzer Type Casting / tip dönüşümü için aşağıdaki sentaksları kullanabilmekteyiz.

  1. <type> syntax
let degisken: any = "bir metin"; 
let metinUzunlugu: number = degisken.length

2 . as-syntax

let degisken: any= "bir metin";
let metinUzunlugu: number = (degisken as string).length

Tip sistemi ve bize sağladığı kolaylıklara genel olarak bakmış olduk. Şimdi sırada ki başlığımıza bakarak devam edelim;

Interfaces in TypeScript – (Arayüzler)

Java yada C# benzeri inteface desteği olan bir dilden geliyorsanız zaten bildiğiniz bir kavram oalcaktır. Sadece söz dizimi olarak Typescirpt için naısl kullanılacağına bakmanız ve Javascript in doğası gereği, sadece sınıflar için değil genel olarak Object{} seviyesinde her şeye uygulanabiliyor olmlarını aklınızda tutmanız yeterli olacaktır.

Örneğin en klasik haliyle aşağıdaki gibi bir interface olsun ;

interface Insan {
  isim: string;
  soyad: string;
  yas: number;
  ekranaYaz():void
}

Yukarıdaki interface i gerçekleyen bir sınıfı aşağıdaki gibi tanımlayabiliriz;

class Ogrenci implements Insan{
 isim: string;
 soyad: string;
 yas: number;
 ekranaYaz():void{
 console.log(`Ogrenci : ${this.isim} ${this.soyad} `)
 }
}

Typescipt tarafında aklımızda tutmamız gereken bir başka konuda, Interface lerin , hiç bir şekilde Javascript e dönüştürülmeyeceği, sadece geliştirme ortamında bir kontrat yada tip desteği olarak kullanabileceğimiz bir özellik. Bu Yönüyle, her hangi bir iş yapmayan ve sadece tip desteği için yazılan TypeScript sınıfları yerine, olabildiğince fazla Interface kullanmak çok daha faydalı olacaktır.

Class in TypeScript – ( Sınıflar)

Javascript tasarim olarak klasik OOP yapısı yerine “prototype-based” diye bilinen farklı bir yapı ile Object ve inheritance mimari sağlamkta. Javascript yeni sürümü ES6 ile birlikte, artık diğer dillerden alşık olduğumuz şekliyle “söz dizimi olarak” class anahtar kelimesini desteklemekte.

Unutmadan hemen tekrar edelim, class tanımlamalarımızı klasik OOP dillerindekine benzer şekilde (Java, C# vs.) yapsakda, Javsacript hala “prototype-based”  bir dil. Arka planda bizim yazdığımız class lar tekrar eski usul “prototype-based” nesneler olarak kullanılmaktalar.  Kısacası, ES6 ile birlikte Javascript e gelen class  yapısı alışık olduğumuz OOP temelli sınıflar degiller, sadece “syntactical sugar over the Objects and prototypes / daha kolay daha temiz kod yazmamız için yeni bir söz dizimi ” olduğunu belirtmiş olalım.

Typescript için ilk örneğimizle devam edelim

class Insan {

}

sınıfımızı oluşturmak için class, anahtar kelimesini kullandık, sınıfımız tıpkı diğer OO dillerdekine benzer şekilde, üyeler ve metodlar içerbilmekte;

class Insan {
   isim: string;
   soyad: string;
   yas: number;
   ekranaYaz() : void {
     console.log(`${this.isim} ${this.soyad} `)
  }
}

başka bir özelliğimizde, “constructor” metodumuz, sınıfımızın örneğini oluşturduğumuzda , ilk çalışacak metodumuz;

class Ogrenci implements Insan {
 isim: string;
 soyad: string;
 yas: number;

 constructor(isim:string, soyad:string , yas:number) {
 this.isim = isim;
 this.soyad= soyad;
 this.yas = yas
 }
 ekranaYaz(): void {
    console.log(`Ogrenci : ${this.isim} ${this.soyad} `)
  }
}

Sınıfımızın bir örneğini oluştururken artık constructor / yapıcı metodumuza parametre geçebileceğiz. Sınıfımızın bir örneği oluşturmak içinde “new” anahtar kelimesini kullacanğız;

let insan1 = new Insan("Ali","Veli",25)
insan1.ekranaYaz() // Ali Veli

Typescript in bize sağladığı bir kolaylığa da değinerek devam edelim; constructor metodumuzda yapacağımız küçük bir değişiklikle sınıf üyelerimizin otomatik olarak tanımlanmasını sağlayabiliriz.

class  Insan {
// isim: string; // Artık ihtiyaç yok
// soyad: string; // Artık ihtiyaç yok
// yas: number; // Artık ihtiyaç yok
 
constructor( public isim:string, public soyad:string , public yas:number) {
 //this.isim = isim; //Artık ihtiyaç yok
 //this.soyad= soyad; //Artık ihtiyaç yok
 //this.yas = yas  // Artık ihtiyaç yok
 }
 ekranaYaz(): void {
 console.log(`Ogrenci : ${this.isim} ${this.soyad} `)
 }
}

constructor metodumuzun parametrelerini , public anahtar kelimesi ile tanımlayarak, sınıfımızın üyelerinin otomatik olarak tanımlanmasını sağlayabilrmekteyiz.

Inheritance / Kalıtım

mevcut bir sınıfın başka bir sınıfı miras alması için extends kelimesini kullanarak kalıtım sağlayabilmekteyiz. Insan sınıfını miras alan bir yeni sınıf yazacak olursak,

class Ogrenci extends Insan{
 
}

Miras aldığımız üst sınıfın üyelerine yada metodlarına ulaşmak için de super() metodunu kullanmaktayız.

class Ogrenci extends Insan{
 constructor(){
 super("Ali","Veli",25)
 super.ekranaYaz()
 }
}

Decorators ( annotation)

Decorators (veya annotation) lar bir çok dilde (C#, Java, Python vs.) hali hazırda kullanılan bir özellik, Angular 2 nin TypeScript ile yazılmasındaki önemli etkenlerden de biri Decorators tanımlama kullanma imkanın sunulması. Javascriptin bir sonraki sürümünde de Decorators tanımlama ve kullanma özelliğinin de gelmesi bekleniyor. TypeScript ile bugün Decorator kullanma imkanımız bulunmakta. 

Java veya C# dan farklı olarak Decorator ler TypeScript / Javascript içinde her zaman fonksiyon olarak tanımlanmaktalar.  Bir sınıfı, metodu yada bir sınıfın bir öğesini dekore edebilecek şekilde kullanılabiliyorlar. Fonksiyon seviyesinde kullanılabilmeleri Javsacript in bir artısı ve böylece esnek bir kullanıları var.

Decorators kavramının genel olarak bize kazandırdığı şey(TypeScript/Javascript açısından) zahmetsiz zenginleştirme ve yetenek artırımı diyebiliriz. Bir decorator bir nesneyi alır( bir sınıf, fonksiyon veya parametre) ve onu zenginleştirip donatarak geri döndürür. Unutulmaması gerekn şey Decorator ler “mutable” dır yani , donatılması için verdiğimiz nesnenin orjinali değiştirilir / zenginleştirilir.

Sentaks olarak  , Typescript içinde Decorator kullanmak için aşağıdaki yapıyı kullanmaktayız;

@dekaroter({opsiyonel parametreler })
class, metod,properties // dekore edilecek nesnemiz

@ işareti ile başlayan dekarotörümüz (parametrede alıyorsa parametreleri ile birlikte) fonksiyon olarak çağırlılır, hemen ardından donatılması/dekore edilmesi istenen nesne kullanılır. decorator ilgili nesneyle ilgili işlemi yapar . Angular 2 içersinde çokca kullanılan @Component Decorator re bakalım;

import {Component} from '@angular/core';
@Component({
selector: 'my-app',
template: '<h1>My First Angular 2 App</h1>'
})
export class AppComponent { }

Yukarıda, @Component  decoratorümüz hemen ardından gelen, AppComponent isimli sınıfımızı, dekore ederek/donatarak bir angular 2 componentine  dönüştürmekte. Ayrıca, parametre olarak bir konfigürasyon nesnesi almakta, bizim örneğimizde, sadece selector ve template değerlerini kullandık.  Angular 2 ile gelen diğer componentleri Angular 2 serisinde yer yer kullanacağımız için burada sadece çalışma mantıklarını anlamay çalışalım.

 TypeScript dökümanlarında Decorator contractları aşağıdaki gibi,

  • class: declare type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void;
  • property: declare type PropertyDecorator = (target: Object, propertyKey: string | symbol) => void;
  • method: declare type MethodDecorator = <T>(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>) => TypedPropertyDescriptor<T> | void;
  • parameter: declare type ParameterDecorator = (target: Object, propertyKey: string | symbol, parameterIndex: number) => void;

Yukarıdaki tanımlamalar kafanızı karıştırmasın, sadece referans için merak edenler bakabilir, yukarıdan anlayacağımız önemli noktalar  ise;

  1. class , property , method ve parametreler için Decorator tanımlayabiliyoruz
  2. 4 tanımda da nihayi çıktı girdiyle aynı referansa sahip yani “mutable” bir durum soz konusu bu yüzden dekore etmek donatmak gibi tabirleri kullanıyoruz.

Şimdi bir kaç tane  Decorator yazarak kullanalım,

TypeScript ile kendi Decoratorlerimizi yazmak

İlk önce sınıf seviyesinde iki Decorator yazalım. Bir ürün sınıfımız olsun, ve ürün grubuna yada kategorisine göre farklı vergi oranına sahip ürünleri kolayca modifiye edecek bir Decorator yazarak başlayalım.  Başlangıç itibariyle sınıfımız ;

@Vergili(20)
class Urun {
 constructor(public isim:string,public fiyat:number){
 
 }
}

Sınıfımızdanda kolayca anlaşılabileceği gibi ürünümüz %20 lik vergi grubundan yada kategorisinden, bunu belirtmek için sınıfımızı @Vergili() isimli bir Decorator ile donattık. Decoratorümüz isteğe bağlı olarak bir vergi oranı parametresi almakta(20) şimdi Decorator ü yazalım,


function Vergili(oran: number = 10) {
    return function (target) {
        target.prototype.vergiliFiyat = function () {
            const vf = this.fiyat + (this.fiyat * oran) / 100
            target.prototype.vergili = vf;
            return vf
        };
    }
}

Decorator fonksiyonumuz, parametre olarak bir vergi oranı almakta, ve eğer bu oran sağlanmazsa varsayılan olarak 10 değeri kullanılmakta. İkinci önemli  nokta ise, Decoratorümüz SInıf seviyesinde uygulandığı, sınıf lar için kullanabileceğimiz, Decorator Contractına baktığımzıda ;

  • class: declare type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void;

Sınıf seviyesinde uyguladığımız Decoratorler sınıfın “constructor / yapıcı”  metodlarını parametre olarak almakta ve target/hedef olarakta yine bu fonksiyonu görmekteler. Bu, bir başka değişle, sınıfımızın örneği oluşurken constructor içinde gerekli modifikasyonu yapmamıza imkan bulmak demek.

Yukarıdaki örneğimizde, sınıfımız için bir metod bir de değişken oluşturduk.(değişkenin pratikde bir gerekliliği yok ama örnek olması açısından ekledim. Metodumuz zaten aynı işi yapıyor )

target.prototype.vergiliFiyat // vergili fiyat hesabini yapan metod
target.prototype.vergili = vf;  // urun.vergili seklinde ulasabielcegimiz sinif elemani

şimdi örnegimizi kullanıp bakalim ;

let u = new Urun("Araba", 2500);
console.log(`Urun Fiyat : ${u.fiyat} , Vergili Fiyat : ${u.vergiliFiyat()} [$[${u.vergili}];

çıktımız aşağıdaki gibi olacaktır;

Urun Fiyat : 2500 , Vergili Fiyat : 3000 [3[3000]p>

Önemli iki noktayı tekrar hatırlatalım, Decorator sınıf seviyesinde oluşturulduğu için parametre olarak aldığımız (target) fonksiyonda da , geri döndürdüğümüz fonksiyonda, sınıfımızın “constructor / yapıcı” metodu. Bundan dolayı , fonksiyonumuzun içinde this i kullanabildik ve sınıfmızın elemanlarına ulaşmış olduk.

Diğer konuda,  Decoratorümüzü yazarken, ES6 le birlikte gelen fat arrow funtions yada lambda expressions dediğimiz daha sade yapıyı kullanmadık, bunun sebebi, this in scope olarak orjinal nesnemizi işaret etmesini istememiz.  Kısacası, Decoratorlerimizi yazarken

return (target) => {
    target.prototype.vergiliFiyat =  () => {
        const vf = this.fiyat + (this.fiyat * oran) / 100
        target.prototype.vergili = vf;
        return vf
    };
}

yukarıdaki gibi fat arrow kullanmak yerine klasik function sentaksını kullanmamız hata olasılığını minumuma indirecektir. Şimdi bir kaç Decorator daha yazarak devam edelim, sınıfımız üzerinde işlem yapacak kişilerle ilgili bir Decorator yazalım; sonrasında sınıf üzerinde Log takibi yapalım.

Urun sınıfımıza bir iki metod daha ekleyelim,  sınıfımızın yeni hali

@YetkiGerekli(["["ADMIN", "DEPOSORUMLUSU"]
@Vergili(20)
class Urun {
    constructor(public isim: string, public fiyat: number) {

    }
    @Log()
    urunSil(urunId: String) {
        return `${urunId} basariyla silindi`
    }
    @Log()
    urunDetay() {
        return `Urun : ${this.isim}, fiyat : ${this.fiyat} , Vergili Fiyat :`
    }
}

Yukarıda sınıfımıza iki metod ekledik, (sil ve detay) iki metodumuz da  Decorator almaktalar, ayrıca sınıf seviyesinde yeni bir Decoratorümüz daha var “@YetkiGerekli([["ADMIN”, "DEPOSORUMLUSU”]/strong>”

ilk olarak sınıf seviyesindeki Decoratorümüz den başlayalaım. @YetkiGerekli Decoratorümüz , parametre olarak bir liste alıyor, kimlerin bu sınıf üzerinde işlem yapabileceğine dair bir kontrol sağlmaya çalışacağız. Böyle  @YetkiGerekli Decoratorümüzü kullandığımız her sınıf için merkezi bir alt yapıya sahip olabiliriz.

function YetkiGerekli(liste: string[]) {
    //fake user
    let user ={
        name:"Ali",
        role:"ADMIN"
    }
    return function(target) {
        target.prototype.yetkilimi = function() {
            target.prototype.kullanici = user.name;
            return yetkiliListesi.indexOf(user.role) >= 0;

        };
    }
}

Şimdi metod seviyesindeki @LOG Decoratorümüzü yazalım. Sınıf seviyesindeki Decoratorlerden farklı olarak 3 parametre alacak;

  • target the method being decorated. / Dekore ettiğimiz metod
  • key the name of the method being decorated. // Metodun ismi
  • value a property descriptor of the given property if it exists on the object, undefined otherwise. The property descriptor is obtained by invoking theObject.getOwnPropertyDescriptor() function. // Metodumuzun orjinal hali ve metod nesnemizin tüm değerleri.

Şimdi ilgili fonksiyonumuzu yazalım;

let Log = function () {
    return (target: any, name: string, descriptor: any) => {

        // Orijinal haline bir referans saklayalim
        //descriptor.value = dekore ettigimiz metodu referans etmekte
        //bu sekilde kullanim zorunlu degil\ ama metodu orjinal halinde
        //geri dondurmeyi garanti haline almak icin basvurdugumuz bir yontem
        //baska dekorator ler icin de orjinal hali korumus olduk
        const originalMethod = descriptor.value;
        //referansi aldiktan sonra, descriptor.value i yani metodumuzu
        //modifiye edelim
        descriptor.value= function (...args: any[]) {
            //metodumuza gecen parametreleri alalim
            const parametreler = args.map(p => JSON.stringify(p)).join();
            //  @Log() dekoratoru sadece kayit tutmakta
            // parametreleri aldiktan sonra orijinal metodu
            //cagirip metodun kendi isini yapmasini sagliyabiliriz
            //baska bir degisle, dekoratorumuzun tamamen isini bitirip
            //metodu oyle cagirmasina gerek yok, ihtiytacimiz olani aldik
            //orijinal metodumuzu cagiriyoruz ornegin(<strong>urunSil()</strong>).
            let sonuc = originalMethod.apply(this, args);
            //log tutma islemini yapalim
            console.log(`Cagrilan Metod : ${name}(${parametreler}) => ${JSON.stringify(sonuc)}`);
            return sonuc;
        }
        //descriptoru geri donduruyoruz
        return descriptor;
    };
};

Yukarıda kodların içinde elimden geldiğince süreci anlatmaya çalıştım, target parametresini hiç kullanmadık çünkü @Log() Decorator hiç bir şeyi modifiye etmiyor ihtiyacımız olmadı yani.  Son olarak kodlarimizin buraya kadar olan halini yazalim ve çıktımıza bakalım

//index.ts

let Log = function () {
    return (target: any, name: string, descriptor: any) => {

        // Orijinal haline bir referans saklayalim
        //descriptor.value = dekore ettigimiz metodu referans etmekte
        //bu sekilde kullanim zorunlu degil\ ama metodu orjinal halinde
        //geri dondurmeyi garanti haline almak icin basvurdugumuz bir yontem
        //baska dekorator ler icin de orjinal hali korumus da olduk
        const originalMethod = descriptor.value;
        //referansi aldiktan sonra, descriptor.value i yani metodumuzu
        //modifiye edelim
        descriptor.value = function (...args: any[]) {
            //metodumuza gecen parametreleri alalim
            const parametreler = args.map(p => JSON.stringify(p)).join();
            // orijinal metodumuzu cagiriyoruz. @Log() dekoratoru sadece
            // kayit tutmakta, parametreleri aldiktan sonra\ orijinal metodu
            //cagirip metodun kendi isini yapmasini sagliyoruz
            let sonuc = originalMethod.apply(this, args);
            //log tutma islemini yapalim
            console.log(`Cagrilan Metod : ${name}(${parametreler}) => ${JSON.stringify(sonuc)}`);
            return sonuc;
        }
        //descriptoru geri donduruyoruz
        return descriptor;
    };
};

const yetkiliListesi = ["ADMI["ADMIN", "DEPOSORUMLUSU", "Muhasebe"]nction Vergili(oran: number = 10) {
    return function (target) {
        target.prototype.vergiliFiyat = function () {
            const vf = this.fiyat + (this.fiyat * oran) / 100
            target.prototype.vergili = vf;
            return vf
        };
    }
}

function YetkiGerekli(liste: string[]) {
    //fake user
    let user = {
        name: "Ali",
        role: "ADMIN"
    }
    return function (target) {
        target.prototype.yetkilimi = function () {
            target.prototype.kullanici = user.name;
            return yetkiliListesi.indexOf(user.role) >= 0;

        };
    }
}


@YetkiGerekli(["ADMI["ADMIN", "DEPOSORUMLUSU"]rgili(20)
class Urun {
    constructor(public isim: string, public fiyat: number) {

    }

    @Log()
    urunSil(Id: String) {
        return `${Id} basariyla silindi`
    }

    @Log()
    urunDetay() {
        return `Urun : ${this.isim}, fiyat : ${this.fiyat} , Vergili Fiyat : ${this.vergili}`
    }

}

let u = new Urun("Araba", 2500);
console.log(`Urun Fiyat : ${u.fiyat} , Vergili Fiyat : ${u.vergiliFiyat()} [${u.v[${u.vergili}]
console.log("Yetki : => ", u.yetkilimi(), "Kullanici : ", u.kullanici)
u.urunSil("urun3")
u.urunDetay();

kodlarımızı tsc ile derleyelim

tsc index.ts --experimentalDecorators --emitDecoratorMetadata --target ES5 --out index.js

sonra çalıştırlaım

node index.js

aşağıdakine benzer bir çıktımız oluşacaktır;

Urun Fiyat : 2500 , Vergili Fiyat : 3000 [3000][3000]i : => true Kullanici : Ali
Cagrilan Metod : urunSil("urun3") => "urun3 basariyla silindi"
Cagrilan Metod : urunDetay() => "Urun : Araba, fiyat : 2500 , Vergili Fiyat : 3000"

Dekoratörler biraz karışık bir konu ama alışınca hayatımızı kolaylaştıracak bir yapı sunmaktalar. Aşağıdaki derleme işlemi kafanızı karıştırdıysa;

tsc index.ts --experimentalDecorators --emitDecoratorMetadata --target ES5 --out index.js

angular veya react üzerinden değil direkt oalrak tsc ile derleme yaptığım için bu şekilde bir kullanımız oldu.

bir sonraki yazıda inşallah Tyepscript ile ilgili bir kaç konuya daha değineceğiz.

Kolay Gele.

Kaynaklar

https://www.typescriptlang.org/docs/ => handbook

https://angular-2-training-book.rangle.io

https://www.typescriptlang.org/docs/handbook/decorators.html#method-decorators

http://blog.wolksoftware.com/decorators-metadata-reflection-in-typescript-from-novice-to-expert-part-3

View story at Medium.com

TypeScript Decorators

 

Leave a Reply

Your email address will not be published. Required fields are marked *