TypeScript vs. JavaScript: Başlama Kılavuzunuz

Yayınlanan: 2022-03-11

TypeScript veya JavaScript? Geliştiriciler bu seçimi sıfırdan web veya Node.js projeleri için düşünürler, ancak bu mevcut projeler için de dikkate alınması gereken bir sorudur. JavaScript'in bir üst kümesi olan TypeScript, JavaScript'in tüm özelliklerine ek olarak bazı ek avantajlar sunar. TypeScript, özünde bizi daha temiz kodlamaya teşvik ederek kodu daha ölçeklenebilir hale getirir. Bununla birlikte, projeler istediğimiz kadar sade JavaScript içerebilir, bu nedenle TypeScript kullanmak ya hep ya hiç önermesi değildir.

TypeScript ve JavaScript Arasındaki İlişki

TypeScript, JavaScript'e açık bir tür sistemi ekleyerek değişken türlerinin katı bir şekilde uygulanmasına izin verir. TypeScript, aktarım sırasında tür kontrollerini çalıştırır; bu, TypeScript kodunu web tarayıcılarının ve Node.js'nin anladığı JavaScript koduna dönüştüren bir derleme biçimidir.

TypeScript ve JavaScript Örnekleri

Geçerli bir JavaScript snippet'i ile başlayalım:

 let var1 = "Hello"; var1 = 10; console.log(var1);

Burada var1 bir string olarak başlar, ardından bir number olur.

JavaScript yalnızca gevşek yazıldığından, var1 herhangi bir zamanda bir dizeden işleve kadar herhangi bir türden bir değişken olarak yeniden tanımlayabiliriz.

Bu kodun çalıştırılması 10 çıktısını verir.

Şimdi bu kodu TypeScript olarak değiştirelim:

 let var1: string = "Hello"; var1 = 10; console.log(var1);

Bu durumda, var1 bir string olduğunu bildiririz. Daha sonra ona TypeScript'in katı tip sistemi tarafından izin verilmeyen bir sayı atamaya çalışırız. Aktarma bir hatayla sonuçlanır:

 TSError: ⨯ Unable to compile TypeScript: src/snippet1.ts:2:1 - error TS2322: Type 'number' is not assignable to type 'string'. 2 var1 = 10;

Transpiler'e orijinal JavaScript snippet'ini TypeScript gibi işlemesi talimatını verecek olsaydık, var1 otomatik olarak var1'in bir string | number string | number . Bu, var1'e herhangi bir zamanda bir string veya number atamamıza izin veren bir var1 birleşim türüdür . Tür çakışmasını çözdüğümüzde, TypeScript kodumuz başarıyla aktarılır. Çalıştırmak, JavaScript örneğiyle aynı sonucu verir.

TypeScript ve JavaScript 30.000 Feet'ten itibaren: Ölçeklenebilirlik Zorlukları

JavaScript her yerde bulunur, her boyuttaki projeye güç verir ve 1990'lardaki emekleme döneminde hayal bile edilemeyecek şekillerde uygulanır. JavaScript olgunlaşmış olsa da, ölçeklenebilirlik desteği söz konusu olduğunda yetersiz kalıyor. Buna göre geliştiriciler, hem büyüklük hem de karmaşıklık açısından büyüyen JavaScript uygulamalarıyla boğuşuyor.

Neyse ki TypeScript, JavaScript projelerini ölçeklendirmeyle ilgili birçok sorunu ele alıyor. İlk üç zorluğa odaklanacağız: doğrulama, yeniden düzenleme ve belgeleme.

doğrulama

Yeni kod ekleme, değiştirme ve test etme gibi görevlere yardımcı olması için entegre geliştirme ortamlarına (IDE'ler) güveniyoruz, ancak IDE'ler salt JavaScript referanslarını doğrulayamaz. Değişkenlerde ve işlev adlarında yazım hatası olasılığını ortadan kaldırmak için kod yazarken dikkatli bir şekilde izleyerek bu eksikliği hafifletiriz.

Sorunun boyutu, nadiren yürütülen kod dallarındaki bozuk referansların kolayca tespit edilemediği, kod üçüncü bir taraftan geldiğinde katlanarak büyür.

Buna karşılık, TypeScript ile, herhangi bir hatanın aktarım zamanında belirleneceğinden emin olarak, çabalarımızı kodlamaya odaklayabiliriz. Bunu göstermek için eski bir JavaScript koduyla başlayalım:

 const moment = require('moment'); const printCurrentTime = (format) => { if (format === 'ISO'){ console.log("Current ISO TS:", moment().toISO()); } else { console.log("Current TS: ", moment().format(format)); } }

.toISO() çağrısı, moment.js toISOString() yönteminin bir yazım hatasıdır, ancak format bağımsız değişkeninin ISO olmaması koşuluyla kod çalışır. ISO fonksiyona ilk geçirmeye çalıştığımızda, bu çalışma zamanı hatasını yükseltecek: TypeError: moment(...).toISO is not a function .

Yanlış yazılmış kodu bulmak zor olabilir. Geçerli kod tabanının bozuk satıra giden bir yolu olmayabilir, bu durumda bozuk .toISO() referansımız test tarafından yakalanmaz.

Bu kodu TypeScript'e aktarırsak, IDE bozuk referansı vurgulayarak düzeltme yapmamızı ister. Hiçbir şey yapmazsak ve aktarmaya çalışırsak, engelleniriz ve aktarıcı aşağıdaki hatayı oluşturur:

 TSError: ⨯ Unable to compile TypeScript: src/catching-mistakes-at-compile-time.ts:5:49 - error TS2339: Property 'toISO' does not exist on type 'Moment'. 5 console.log("Current ISO TS:", moment().toISO());

yeniden düzenleme

Üçüncü taraf kod referanslarındaki yazım hataları olağandışı olmasa da, dahili referanslardaki yazım hatalarıyla ilgili aşağıdaki gibi farklı bir dizi sorun vardır:

 const myPhoneFunction = (opts) => { // ... if (opts.phoneNumbr) doStuff(); }

Tek bir geliştirici, yeterince kolay bir şekilde sona er için phoneNumbr tüm örneklerini bulabilir ve düzeltebilir.

Ancak ekip ne kadar büyük olursa, bu basit, yaygın hata o kadar fazla maliyetlidir. İşlerini gerçekleştirirken meslektaşların bu tür yazım hatalarının farkında olmaları ve yaymaları gerekir. Alternatif olarak, her iki yazımı da desteklemek için kod eklemek, kod tabanını gereksiz yere şişirir.

TypeScript ile, bir yazım hatasını düzelttiğimizde, bağımlı kod artık aktarılmayacaktır ve iş arkadaşlarına düzeltmeyi kendi kodlarına yaymaları için sinyal gönderecektir.

belgeler

Doğru ve ilgili belgeler, geliştirici ekipleri içinde ve arasında iletişimin anahtarıdır. JavaScript geliştiricileri, beklenen yöntem ve özellik türlerini belgelemek için genellikle JSDoc kullanır.

TypeScript'in dil özellikleri (örneğin, soyut sınıflar, arayüzler ve tip tanımları), tasarım bazında programlamayı kolaylaştırarak kaliteli belgelere yol açar. Ayrıca, bir nesnenin uyması gereken yöntemlerin ve özelliklerin resmi bir tanımına sahip olmak, kırılma değişikliklerini belirlemeye, testler oluşturmaya, kod iç gözlemi gerçekleştirmeye ve mimari kalıpları uygulamaya yardımcı olur.

TypeScript için, go-to aracı TypeDoc (TSDoc teklifine dayalı olarak), kodumuzdan tür bilgilerini (örn. sınıf, arayüz, yöntem ve özellik) otomatik olarak çıkarır. Böylece, zahmetsizce JSDoc'unkinden çok daha kapsamlı belgeler oluşturuyoruz.

TypeScript'in JavaScript'e Karşı Avantajları

Şimdi, bu ölçeklenebilirlik zorluklarını gidermek için TypeScript'i nasıl kullanabileceğimizi keşfedelim.

Gelişmiş Kod/Yeniden Düzenleme Önerileri

Birçok IDE, biz kodlarken referans doğrulama sağlayarak TypeScript tipi sistemden gelen bilgileri işleyebilir. Daha da iyisi, biz yazarken, IDE herhangi bir başvuru için ilgili, bir bakışta belgeler (örneğin, bir işlevin beklediği argümanlar) sunabilir ve bağlamsal olarak doğru değişken adları önerebilir.

Bu TypeScript parçacığında, IDE, işlevin dönüş değeri içindeki anahtar adlarının otomatik olarak tamamlanmasını önerir:

 /** * Simple function to parse a CSV containing people info. * @param data A string containing a CSV with 3 fields: name, surname, age. */ const parsePeopleData = (data: string) => { const people: {name: string, surname: string, age: number}[] = []; const errors: string[] = []; for (let row of data.split('\n')){ if (row.trim() === '') continue; const tokens = row.split(',').map(i => i.trim()).filter(i => i != ''); if (tokens.length < 3){ errors.push(`Row "${row}" contains only ${tokens.length} tokens. 3 required`); continue; } people.push({ name: tokens[0], surname: tokens[1], age: +tokens[2] }) } return {people, errors}; }; const exampleData = ` Gordon,Freeman,27 G,Man,99 Alyx,Vance,24 Invalid Row,, Again, Invalid `; const result = parsePeopleData(exampleData); console.log("Parsed People:"); console.log(result.people. map(p => `Name: ${p.name}\nSurname: ${p.surname}\nAge: ${p.age}`) .join('\n\n') ); if (result.errors.length > 0){ console.log("\nErrors:"); console.log(result.errors.join('\n')); }

IDE, Visual Studio Code, işlevi çağırmaya başladığımda (satır 31) bu öneriyi sağladı (belirtimde):

parsePeopleData() yazma noktasında, IDE, TypeScript aktarıcısından "parsePeopleData(veri: dize): { kişi: { ad: dize; soyadı: dize; yaş: sayı; }[]; hataları okuyan bir araç ipucu gösterir: string[]; }" ve ardından işlev tanımından önceki çok satırlı yorumda yer alan metin, "3 alanlı bir CSV içeren bir dize: ad, soyadı, yaş. Kişi bilgilerini içeren bir CSV'yi ayrıştırmak için basit işlev.".

Dahası, IDE'nin otomatik tamamlama önerileri (belirtilen metinde) bağlamsal olarak doğrudur ve yalnızca iç içe anahtar durumunda (satır 34) geçerli adları gösterir:

"map(p => `Ad: ${p." yazıldığında ortaya çıkan üç öneri (yaş, ad ve soyadı) İlk öneri vurgulanır ve yanında "(mülk) yaş: sayı" bulunur.

Bu tür gerçek zamanlı öneriler daha hızlı kodlamaya yol açar. Ayrıca, IDE'ler, kodu herhangi bir ölçekte yeniden düzenlemek için TypeScript'in titiz tür bilgilerine güvenebilir. Bir özelliği yeniden adlandırma, dosya konumlarını değiştirme ve hatta bir üst sınıf çıkarma gibi işlemler, referanslarımızın doğruluğundan %100 emin olduğumuzda önemsiz hale gelir.

Arayüz Desteği

JavaScript'in aksine TypeScript, arayüzleri kullanarak türleri tanımlama yeteneği sunar. Bir arabirim, bir nesnenin içermesi gereken yöntemleri ve özellikleri resmi olarak listeler - ancak uygulamaz. Bu dil yapısı, diğer geliştiricilerle işbirliği için özellikle yararlıdır.

Aşağıdaki örnek, ortak OOP kalıplarını düzgün bir şekilde (bu durumda, strateji ve sorumluluk zinciri) uygulamak için TypeScript'in özelliklerinden nasıl yararlanabileceğimizi vurgular ve böylece önceki örneği geliştirir:

 export class PersonInfo { constructor( public name: string, public surname: string, public age: number ){} } export interface ParserStrategy{ /** * Parse a line if able. * @returns The parsed line or null if the format is not recognized. */ (line: string): PersonInfo | null; } export class PersonInfoParser{ public strategies: ParserStrategy[] = []; parse(data: string){ const people: PersonInfo[] = []; const errors: string[] = []; for (let row of data.split('\n')){ if (row.trim() === '') continue; let parsed; for (let s of this.strategies){ parsed = s(row); if (parsed) break; } if (!parsed){ errors.push(`Unable to find a strategy capable of parsing "${row}"`); } else { people.push(parsed); } } return {people, errors}; } } const exampleData = ` Gordon,Freeman,27 G;Man;99 {"name":"Alyx", "surname":"Vance", "age":24} Invalid Row,, Again, Invalid `; const parser = new PersonInfoParser(); const createCSVStrategy = (fieldSeparator = ','): ParserStrategy => (line) => { const tokens = line.split(fieldSeparator).map(i => i.trim()).filter(i => i != ''); if (tokens.length < 3) return null; return new PersonInfo(tokens[0], tokens[1], +tokens[2]); }; parser.strategies.push( (line) => { try { const {name, surname, age} = JSON.parse(line); return new PersonInfo(name, surname, age); } catch(err){ return null; } }, createCSVStrategy(), createCSVStrategy(';') ); const result = parser.parse(exampleData); console.log("Parsed People:"); console.log(result.people. map(p => `Name: ${p.name}\nSurname: ${p.surname}\nAge: ${p.age}`) .join('\n\n') ); if (result.errors.length > 0){ console.log("\nErrors:"); console.log(result.errors.join('\n')); }

ES6 Modülleri—Her Yerde

Bu yazı itibariyle, tüm ön uç ve arka uç JavaScript çalışma zamanları ES6 modüllerini desteklememektedir. Ancak TypeScript ile ES6 modül sözdizimini kullanabiliriz:

 import * as _ from 'lodash'; export const exampleFn = () => console.log(_.reverse(['a', 'b', 'c']));

Aktarılan çıktı, seçilen ortamımızla uyumlu olacaktır. Örneğin, --module CommonJS derleyici seçeneğini kullanarak şunları elde ederiz:

 "use strict"; exports.__esModule = true; exports.exampleFn = void 0; var _ = require("lodash"); var exampleFn = function () { return console.log(_.reverse(['a', 'b', 'c'])); }; exports.exampleFn = exampleFn;

Bunun yerine --module UMD'yi kullanarak --module UMD daha ayrıntılı UMD desenini verir:

 (function (factory) { if (typeof module === "object" && typeof module.exports === "object") { var v = factory(require, exports); if (v !== undefined) module.exports = v; } else if (typeof define === "function" && define.amd) { define(["require", "exports", "lodash"], factory); } })(function (require, exports) { "use strict"; exports.__esModule = true; exports.exampleFn = void 0; var _ = require("lodash"); var exampleFn = function () { return console.log(_.reverse(['a', 'b', 'c'])); }; exports.exampleFn = exampleFn; });

ES6 Sınıfları—Her Yerde

Eski ortamlar genellikle ES6 sınıfları için destekten yoksundur. TypeScript aktarımı, hedefe özel yapılar kullanarak uyumluluğu sağlar. İşte bir TypeScript kaynak parçacığı:

 export class TestClass { hello = 'World'; }

JavaScript çıktısı, TypeScript'in belirlememize izin verdiği hem modüle hem de hedefe bağlıdır.

--module CommonJS --target es3 verir:

 "use strict"; exports.__esModule = true; exports.TestClass = void 0; var TestClass = /** @class */ (function () { function TestClass() { this.hello = 'World'; } return TestClass; }()); exports.TestClass = TestClass;

Bunun yerine --module CommonJS --target es6 kullanarak aşağıdaki aktarılmış sonucu elde ederiz. ES6'yı hedeflemek için class anahtar sözcüğü kullanılır:

 "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.TestClass = void 0; class TestClass { constructor() { this.hello = 'World'; } } exports.TestClass = TestClass;

Async/Await İşlevselliği—Her Yerde

Async/await, zaman uyumsuz JavaScript kodunun anlaşılmasını ve bakımını kolaylaştırır. TypeScript, bu işlevi, yerel olarak zaman uyumsuz/bekleme sağlamayanlara bile, tüm çalışma zamanlarına sunar.

ES3 ve ES5 gibi daha eski çalışma zamanlarında async/await'i çalıştırmak için Promise tabanlı çıktı için harici desteğe ihtiyacınız olacağını unutmayın (örneğin, Bluebird veya bir ES2015 polyfill aracılığıyla). TypeScript ile birlikte gelen Promise çoklu doldurma, aktarılan çıktıya kolayca entegre olur; yalnızca lib derleyici seçeneğini buna göre yapılandırmamız gerekir.

Özel Sınıf Alanları Desteği—Her Yerde

TypeScript, eski hedefler için bile private alanları güçlü yazılan dillerle (örn. Java veya C#) aynı şekilde destekler. Buna karşılık, birçok JavaScript çalışma zamanı, ES2022'nin tamamlanmış bir teklifi olan karma önek sözdizimi aracılığıyla private alanları destekler.

TypeScript ile JavaScript'in Dezavantajları

TypeScript uygulamasının başlıca avantajlarını vurguladığımıza göre, TypeScript'in uygun olmayabileceği senaryoları inceleyelim.

Aktarım: İş Akışı Uyumsuzluğu Potansiyeli

Belirli iş akışları veya proje gereksinimleri, TypeScript'in aktarma adımıyla uyumlu olmayabilir: örneğin, dağıtımdan sonra kodu değiştirmek için harici bir araç kullanmamız gerekirse veya oluşturulan çıktının geliştirici dostu olması gerekiyorsa.

Örneğin, yakın zamanda bir Node.js ortamı için bir AWS Lambda işlevi yazdım. TypeScript uygun değildi çünkü aktarmanın gerekmesi benim ve diğer ekip üyelerinin AWS çevrimiçi düzenleyicisini kullanarak işlevi düzenlemesine engel olacaktı. Bu, proje yöneticisi için bir anlaşma kırıcıydı.

Tip Sistem Sadece Nakil Zamanına Kadar Çalışır

TypeScript'in JavaScript çıktısı tür bilgisi içermez, bu nedenle tür kontrolleri yapmaz ve bu nedenle tür güvenliği çalışma zamanında bozulabilir. Örneğin, her zaman bir nesneyi döndürmek için bir işlevin tanımlandığını varsayalım. Bir .js dosyası içindeki kullanımından null döndürülürse, bir çalışma zamanı hatası oluşur.

Tür bilgisine bağlı özellikler (örneğin, özel alanlar, arayüzler veya jenerikler) herhangi bir projeye değer katar, ancak aktarım sırasında silinir. Örneğin, private sınıf üyeleri aktarımdan sonra artık özel olmayacaktı. Açık olmak gerekirse, bu nitelikteki çalışma zamanı sorunları TypeScript'e özgü değildir ve JavaScript'te de aynı zorluklarla karşılaşmayı bekleyebilirsiniz.

TypeScript ve JavaScript'i Birleştirme

TypeScript'in birçok avantajına rağmen, bazen tüm JavaScript projesini bir kerede dönüştürmeyi doğrulayamayız. Neyse ki TypeScript aktarıcısına dosya bazında nelerin düz JavaScript olarak yorumlanacağını belirtebiliriz. Aslında, bu hibrit yaklaşım, bir projenin yaşam döngüsü boyunca ortaya çıkan bireysel zorlukların azaltılmasına yardımcı olabilir.

Aşağıdaki durumlarda JavaScript'i değiştirmeden bırakmayı tercih edebiliriz:

  • Eski bir meslektaş tarafından yazılmıştır ve TypeScript'e dönüştürmek için önemli tersine mühendislik çalışmaları gerektirir.
  • TypeScript'te izin verilmeyen teknikleri kullanır (örneğin, nesne somutlaştırmasından sonra bir özellik ekler) ve TypeScript kurallarına uymak için yeniden düzenleme gerektirir.
  • JavaScript kullanmaya devam eden başka bir ekibe ait.

Bu gibi durumlarda, bir bildirim dosyası ( .d.ts dosyası, bazen tanım dosyası veya yazım dosyası olarak adlandırılır), JavaScript kodunu olduğu gibi bırakırken IDE önerilerini etkinleştirmek için TypeScript'e yeterli tür verisi verir.

Birçok JavaScript kitaplığı (ör. Lodash, Jest ve React), TypeScript yazım dosyalarını ayrı tür paketlerinde sağlarken, diğerleri (ör. Moment.js, Axios ve Luxon) yazım dosyalarını ana pakete entegre eder.

TypeScript vs. JavaScript: Kolaylaştırma ve Ölçeklenebilirlik Sorunu

TypeScript aracılığıyla sağlanan rakipsiz destek, esneklik ve geliştirmeler, geliştirici deneyimini önemli ölçüde iyileştirerek projelerin ve ekiplerin ölçeklenmesini sağlar. TypeScript'i bir projeye dahil etmenin başlıca maliyeti, aktarım oluşturma adımının eklenmesidir. Çoğu uygulama için JavaScript'e aktarma bir sorun değildir; daha ziyade, TypeScript'in birçok avantajına giden bir basamaktır.


Toptal Mühendislik Blogunda Daha Fazla Okuma:

  • TypeScript ve Jest Desteğiyle Çalışmak: Bir AWS SAM Eğitimi