Neden Typescript?
Typescript’in nedenlerine başlamadan önce biraz Development sürecine değinmek istiyorum.
Backend, Frontend Developer olmak
Sektörde Developer birkaç farklı kategoriye ayrılmış durumda. Backend Developer, Frontend Developer veya Fullstack Developer olmak üzere. Tabi bunların daha spesifik kategorileri olsa da genel anlamda bunları ele almanın yeterli olduğunu düşünüyorum. Tanımları kısmen belirlenmiş olsa da kapsamları hala net olabilmiş değil. Örneğin; Backend Developer bazen DevOps + Development bazı yerlerde birazcık DevOps + Development bazılarında ise sadece Development. Diğerleri için de benzer durumları söz konusu.
Fullstack Developer olarak çalıştığım için iki taraftan da olayı ele alabildiğimi düşünüyorum. Aslında Fullstack olarak çalışan birçok kişinin genel olarak Backend Developer’a yakın olduğunu Frontend’i de gereklilikten dolayı yaptığını söyleyebilirim.
Backend developer açısından baktığımda Backend yazarken genel olarak yapacağınız işe odaklanırken frontend tarafında bu bazen çığrından çıkıp kullanılan tarayıcı (Chrome, Firefox, IE, Opera, Safari) hatta ondan ziyade tarayıcının çalıştığı cihaza kadar hesaplamalar gerektirebiliyor. Tabii ki backend tarafında da bazen işletim sistemine göre bir şeyler yapmak gerekebilir; ancak bu üst seviyede yapılan bazı durumlar için söz konusu. Frontend tarafında ise bazen çok basit bir işlemde bile bunu düşünmeniz gerekir ki bundan dolayı Backend Developer’lar Frontend tarafını bir problem yumağı olarak görür.
Frontend’de Çözüm Arayışı
Ekip olarak Fullstack Developer olarak çalıştığımız için Frontend tarafında işin kolayına kaçmak istedik. Bir şekilde Frontend’i basitleştirmek (automatize) istedik. Bu yüzden Kendo UI alt yapısını kullanan UI kütüphanesi tasarladık; ancak yeni başlayan geliştiricilerin var olan koda adaptasyonunda büyük problemler yaşadık. Bunun üzerinde React ile tanıştık. Ve yeniden bir framework tasarlayarak UI tarafını daha basit hale getirmeye çalıştık. Tabi sadece React anlaşılırlık katmayacağından Javascript kısmında ES6, UI için react-bootstrap ve development ortamı için gulp, webpack , grunt, babel gibi bir çok tool’u öğrenip alt yapı sağlayarak UI kısmını en basit hale getirmeye çalıştık. Durum böyle olunca aslında problemlerinden kaçtığımız Frontend tarafının bir parçası olduk. :)
Neden Typescript ?
ES6 yazarken bir şeyler eskisine göre düzenliydi, anlaşılırdı. En azından koda yeni bir ekleme yaptığımda daha hatasız ve anlaşılır bir ürün çıkıyordu ama geliştirme sürecini biraz daha iyileştirmek istedim. Bu yüzden çözüm arayışlarına girdim. Peki neydi bu eksiklikler?
- Çalıştırdığım metotta ne dönüyor ? Daha anlaşılır olması gerekmez mi?Özellikle kütüphane incelerken. Kullanmak istediğim kütüphaneyi incelerken dökümantasyonunu da incelemeliyim; çünkü tüm kütüphaneler detaylı dökümantasyona sahip olmayabilir. Aksi durumda javascript debugger’dan inceleme yaparak keşfe çıkmak gerekir ki bu da development sürecini uzatan zahmetli bir işlem.
- Metodu private yaparken
__
'mi kullanmalayım. Bu şekilde kullanmakta ne demek?! Neden bir identifier yokki bunun için? - Compile işlemi için hangi babel transformer’in ihtiyacım var? state0 , stage1, stage2 mi bunlar ne ki ?
- ES7'de static mi var? ES7 transformer daha çıkmadı mı ? Onun yerine babel’in ES7 static desteği ekleyen transformer’imi.
Bu şekilde sorunlar birikirken ben typescript ile tanıştım. Tamam da typescript Microsoft tarafından geliştirmiş bir compiler. Geliştiren Microsoft olunca eskiden de var olan bir önyargı ile görmezden geliyorum.
Typescript’in ürettiği çözümlerin bir kısmını ES6'da Flow ve Eslint kullanarak gidermeye çalıştım. Flow içinde farklı çözümler vardı; ancak bir tarafı düzeltiyordun, başka tarafta problemler oluyordu. Hiçbiri yeteri kadar kapsamlı düşünülmemişti. Bu yüzden problemleri tamamen çözemiyordum. Ya da bulduğum çözümler beni yeterince tatmin etmiyordu. Performanstan kayıp vs. derken bir gün typescript yazarken buldum kendimi. Ilk başlarda o da çok zor geliyordu. Ama daha önceki savaşlardan sonra bu savaşa da girmeye hazırdım ve küçük küçük bir çok deneme (kütüphane yazma çabaları) yaptım. Sonunda yani yaklaşık 1–2 ay sonra evet Typescript benim Frontend’e aradığım yazım şekli dedim. Peki neden bunu diyebildim? Aşağıdaki maddelerde bunu açıklayacağım.
- Tek compiler ile tüm transform işlemleri yapabilme.
- ES6 ile yazdığım her şeyi yapabilme, ayrıca artı birçok özellik.
- Public, Private, Protected gibi daha önce aşina olduğum anlaşılırlığı yükselten yazım tarzları.
- Java’ya benzer bir jenerik yapısı.
- Abstract, Enum gibi daha da özelleşmiş yapılar.
- Hiçbir runtime maliyeti bulunmayan interface’ler.(Frontend’de kod boyutu da maliyetten olduğundan)
- Tek dosyada yapılan configurasyonlar ile tüm işlemlerin yapılabilmesi.
- Hiçbir aracı araç kullanmadan build işlemlerininin yapılabilmesi.
- Development için webpack ile entegrasyonu
En önemlisi ;
- Yazdığım kodu çağırırken tekrar tekrar kodu açıp bakmama gerek kalmıyor.
- Koda baktığımda anlaşılırlık seviyesi yüksek bir kod görebiliyorum.
- Bir kodu yazarken (ki bazen 5–10 class’tan oluşan birşeyler yazıyordum tek seferde) kodum nerdeyse hatasız çalışıyordu. Yani ben javascript yazacağım ve ilk denememde çalışacak. Normalde yok böyle bir şey!
- IDE Desteği ( Özellikle Webstrom ve Visual Studio Code ) kullandığınızda typescript kullanmanın marifetini görebilirsiniz. Çünkü Typed bir dil olduğundan IDE Asistant size yardımcı olabiliyor.
- React ile entegre etme ve aynı süreci React Component’lerinde yapabilme. JSX kullandığınızda bir componentte bir attribute eklediğinizde Attribute var mı ? Opsiyonel mi ? Required mi ? gibi hataları Runtime’da anlayabilirsiniz; Ancak bu entegrasyonla tasarım aşamasında görebilmeniz mümkün. Yani probemlerleri tasarım aşamasında anlayabilirsiniz.
Peki , Hiç mi Dezavantajı yok
Tabiki dezavantajları mevcut. Örneğin ;
- Debugging yapma
- Typescript desteği olmayan kütüphaneleri kullanmak
- Dinamik nesneler oluşturma (İçeriği belli olmayan dinamik nesneler)
Ancak bu dezavantajları da yok etmek mümkün tabii.
- Debug kısmı tamamen configurasyonuna bağlı bir durum. Araştırarak çözülebilecek bir durum.
- Eğer typescript desteği mevcut değilse birkaç farklı şekilde problemi çözebiliriz. Typescript desteği istemiyorsak, import yerine require kullanmak. Yani node require özelliği. Bu durumda typescript bunun için herhangi bir compile işlemi gerçekleştirmiyor. Yada eğer typescript destekli olmasını istiyorsanız kütüphane için tanım sınıfları oluşturma (Typescript definitions ).
- Bir yerde dinamizm varsa typescript’te any demeniz yeterli olur. Bu durumda o tipi kullanırken javascript kullanmaktan farkı kalmaz.
Yani anlayacağınız aslında dezavantajlar hali hazırda var olan ES6 veya javascript’e göre zaten dezavantaj değil. Sadece beklentilerimize göre dezavantaj sayılabilir ancak şuan hatırlamadığım bir çok problemide aynı şekilde uğraşarak çözdüm diyebilirim. Ve sonuç olarak şu an bildiğim frontend dünyasında avantajlarından bahsedebileceğim bir dil kullanmaya başladım diyebilirim.
Bu kadar teorik anlattık durduk, şimdi tüm hikayeyi örnekleyelim değil mi .
Kütüphanelerin typescript tanımlamaları
Tanımlamalar birkaç farklı şekilde olabilir
- Kütüphane içerisinde d.ts(typescript tanım dosya uzantısı)’leri tutarak
- Kütüphanenin typescript tanım dosyalarını barındırması. Örneğin
mobx
kütüphanesi içerisinde typescript tanımlamalarını barındırır. O yüzden
npm install --save mobx
demek dışında bir şeye ihtiyacınız olmaz.
- https://github.com/DefinitelyTyped/DefinitelyTyped adresinde paylaşılmış tanımları kullanarak.
npm install --save react
npm install --save-dev @types/react
Örnekte react dependency’lere eklerken typescript tanım kütüphanesini de dev-dependency olarak ekliyoruz.
- Proje içerisinde .d.ts dosyalarını tutarak.
Export, Import işlemleri
ES6'dan gelen bir kaç özelliğin tamamını typescript içerisinde bulabilirsiniz.
Typescript bilgisine geçmeden önce birkaç tanımı bilmekte fayda var.
module.exports
nodejs’te mevcut olan module.exports commonjs yönteminde bir dosya başka bir yerden alındığında (import edildiğinde) kullanıma açılan nesneleri tutan bir referanstır diyebiliriz. Yani bir nesneyi bulunduğu tanım yeri dışında kullanılabilmesini istiyorsak module.exports’a bu nesnenin eklenmesi gerekir. Bir nevi nesneyi public
yapmak diyebiliriz.
Örneğin;
// a.js
var ex = {
test: "Bu bir testir",
fnTest: function(){ }
}module.exports = ex
// b.js
// b.js
const ex = require("./a");
Örnekte ex isminde bir değişken tanımladık ve buna bazı nesneler ekledik.
Bu nesneyi module.exports = ex
şeklinde dış dünyaya açtık . Bunu yapmamış olsaydık bu dosya içerisinde ex
değişkeni kullanılabilirken require
edildiği yerde kullanılamayacaktı.
Tabiki bunları dışarı vermenin başka yollarıda mevcut. UMD veya AMD yöntemlerini kullanarak. Ancak nodejstarafından varsayılan olarak kullanılan commonjs yöntemidir diyebiliriz.
default
ES6 ile gelen default sözcüğü aslında basit bir taktik diyebiliriz. module.exports işlemi yapılırken birden fazla export kullanılabilir. Bu durumda belirteç olmadan çağırım yapıldığında hangisinin geleceğini belirlemek için defaultkeyword’ü kullanılır.
Örneğin;
var ex = {
default: "Bu default exporttur."
test: "Bu bir testir",
fnTest: function(){ }
}module.exports = ex
Biz eğer ES6 veya typescript kullanarak import a from "./a.js"
şeklinde çağırırsak a değeri "Bu default exporttur." default değerini taşır.
export
export let key = object
, export default object
Aslında module.exports
işleminin yeni kullanım şeklidir diyebiliriz. Aşağıdaki örneklerden anlayacağınız gibi export aynı sayfada defalarca çağrılarak istenildiği kadar module export edilebilir. module.exports[key]=value
şeklinde kullanmak diye düşünebiirsiniz.default
export ise sadece default keywordune bir elemanın atanmasıdır. Burada dikkat etmeniz gereken her zaman sadece bir export default
edebilirsiniz.
- export interface
Interface’ler sadece typescript’te geçerli yapılardır. Sadece type referans olarak gösterilebilirler. Değer olarak kullanılamazlar.
// module.exports.StringValidator = StringValidator demekle benzer bir işlem.
// bu işlem interface üzerinden olduğundan sadece typescript içerisinde kullanılan bir veri olduğunu bilmeliyiz.
export interface StringValidator {
isAcceptable(s: string): boolean;
}
- export const object
// module.exports.numberRegexp = /^[0-9]+$/; demekle benzer bir işlem.
export const numberRegexp = /^[0-9]+$/;
- export class
export class ZipCodeValidator implements StringValidator {
isAcceptable(s: string) {
return s.length === 5 && numberRegexp.test(s);
}
}
- export in block
export işlemi yaparken farklı isimde export edebilirsiniz.
class ZipCodeValidator implements StringValidator { // module.exports.ZipCodeValidator = {ZipCodeValidator classı}
isAcceptable(s: string) {
return s.length === 5 && numberRegexp.test(s);
}
}export { ZipCodeValidator }; // module.exports.ZipCodeValidator
= {ZipCodeValidator classı}
- export as
export işlemi yaparken farklı isimde export edebilirsiniz.
class ZipCodeValidator implements StringValidator { // module.exports.ZipCodeValidator = {ZipCodeValidator classı}
isAcceptable(s: string) {
return s.length === 5 && numberRegexp.test(s);
}
}
export { ZipCodeValidator as mainValidator }; //
- export all
export edilecek nesnenin tüm içeriğini export edebilirsiniz.
export * from "./StringValidator"; // exports interface 'StringValidator'
export default $; // module.exports.default = $ ( jquery )
- export default
default olacak şekilde export işlemi yapabilirsiniz.
export default class ZipCodeValidator {
}
import
import işlemi module.exports
ile çıkarılan yani export
edilen tüm module'lerin import edilmesi sağlar. Ancak import ederek birkaç farklı yöntem ile import edebiliriz.
- import default from “./dosya”;
Bu işlem ile default olarak belirtilmiş nesne import edilir.
// a.js
export default class Example {
}
// b.js
import Example from "./a";
- import { Example, Example2 } from “./dosya”;
Bu işlem ile default dışında export edilmiş olan nesneleri alabiliriz.
// a.js
export class Example {
}
export class Example2 {
}
// b.js
import { Example, Example2 } from "./a";
import * as Hepsi from “./dosya”;
Bu işlem ile module.exports
çıktısını import etmiş oluruz. Yani tüm export'lari bir obje içerisinde import etmiş oluruz.
Not: Bu özellik es6'da mevcut değil diyebiliriz. Bunun yerinde es6'da default mevcut değilse otomatik hepsini import etmiş oluruz.
// a.js
export class Example {
}
export class Example2 {
}
export default class DefaultExample {
}
// b.js
import * as Hepsi from "./a";
console.log(Hepsi.Example);
console.log(Hepsi.Example2);
console.log(Hepsi.default); // DefaultExample nesnesi döndürür.
- class , abstract, enum
Class tanımı
class
keyword'u ile yapılır.
class Example {
}
export default Example
abstract
tanımı iseclass
keyword'unden once yapılır
abstract class Example {
}export default Example;
enum
enum Direction {
Up = 1,
Down,
Left,
Right
}
export default Direction;
String enum tanımı
enum Colors {
Red = "RED",
Green = "GREEN",
Blue = "BLUE",
}
export default Colors;
- public, private, protected
Public erişimcisi ile belirtilen class üyeleri (field, member) dışarıdan erişilebilir olur.
class Example {
public test(){
}
}
export default Exampleimport Example from "./Example"let example = new Example();
console.log(example.test()); // Erişebiliriz.
protected erişimcisi ile belirtilen class üyeleri (field, member) subclass’lar tarafından erişilebilir olur.
class Example {
protected test(){
}
}
export default Exampleimport Example from "./Example"class MyExample extends Example {
public test2(){
this.test(); // erişilebilir.
}
}
// let example = new Example();
// console.log(example.test()); // Hatalı erişim.let example = new MyExample();
console.log(example.test2());
private erişimcisi ile belirtilen class üyeleri (field, member) sadece class içerisinden erişilebilir olur.
class Example {
privat test(){
}
public test2(){
this.test(); // erişilebilir.
}
}
export default Exampleimport Example from "./Example"let example = new Example();
console.log(example.test2());
// public olan test2 methoduna erişiriz. Oda içerisinde test() methoduna erişebilir.
- constructor
Sınıfın oluşturulması için gereken metottur.
class Example { public constructor(){
}
}export class Example2 {
protected constructor(){
}
}export class Example3 {
private constructor(){
}
}export default Example
- static
class Example {
public static test(){
}
}
Example.test() // static method kullanımı
export default Example
- Generic Tip tanımlamaları
Generic Tip tanımlamaları ile birden farklı tip için kullanılabilen sınıflar yazabilirsiniz. Örneğin aşağıdaki Iterator sınıfı herhangi bir tip için Array iterasyonu yapmamızı sağlar.
export default class Iterator<E> {
private array: Array<E>; public constructor(array: Array<E>) {
this.array = array.slice(0);
} /**
* Returns {@code true} if the iteration has more elements.
* (In other words, returns {@code true} if {@link #next} would
* return an element rather than throwing an exception.)
*
* @return {@code true} if the iteration has more elements
*/
public hasNext(): boolean {
return this.array.length > 0;
} /**
* Returns the next element in the iteration.
*/
public next(): E {
return this.array.shift();
} /**
*
* @param {(element: E) => any} callback
*/
public forEachRemaining(callback: (element: E) => any) {
while (this.hasNext()) callback(this.next());
}
}
- interface
Tipleri tanımlamak için kullanılan ve hiç bir runtime maliyeti bulunmayan sınıfları diyebiliriz. Yani bir interface ile tip belirtilmelirini yapabilirsiniz. Ancak javascript’e dönüştürülen kod da bu interface tanımını bulamazsınız. Yani interface tanımı kodun typescript sürecinde düzenli olmasını sağlar. Javascript’te dönüştürülen kod üzerinde Typescriptin herhangi bir hükmü kalmaz.
Örneğin;
export interface Deneme {
name: string // bu alan zorunlu bir alanladır.
surname?: string // Bu alan zorunlu değildir.
}let a: Deneme = {
name: "Kamil"
};// let a: Deneme = {} // Bu şekilde tanımladığımızda typescript hatalı işlem olarak görür. Çünkü Deneme tipi name isminde zorunlu bir alan taşımaktadır.
export interface SubDeneme extends Deneme {
surname: string // Artık bu alan zorunlu bir alandır.
}
gibi bir çok işlemi interface il yapmak mümkün. Burdaki kod compile olduğunda ise göreceğiz şeklindedir. Yani interface tanımlamaları compile işleminden sonra gereksiz olduğundan build’e eklenmez.
var a = {
name: "Kamil"
};
Daha fazlası için;
- Typescript dökümanı olarak https://basarat.gitbooks.io/typescript dökümanını inceleyebilirsiniz.
- Örnek olarak wasabi-common kütüphanesini inceleyebilirsiniz. Bu kütüphane içerisinde Typescript kullanımı ile ilgili bir çok yapıyı görebilirsiniz.
- Typescript NodeJS Template olarak ts-library-skeleton projesini kullanabilirsiniz.
- Typescript, React Web Template olarak wasabi-web-ui-skeleton projesini kullanabilirsiniz.
- Typescript, React-Native Template olarak rnts-skeleton projesini kullanabilirsiniz.