Anasayfa » 10. TypeScript’de Modüller

10. TypeScript’de Modüller

by Levent KARAGÖL
16 dakikalık okuma süresi

Bu makalemizde, TypeScript’de modüllerin ne olduğunu ve neden modüllere ihtiyaç duyduğumuzu, temel modül tanımı ve kullanımını, export ve import ile modüllerin dışarı/içeri aktarım seçeneklerini, 3. parti kütüphanelerin modül olarak içeri aktarımı ve TypeScript desteği olmayan kütüphaneler için deklarasyon dosyalarının içeri aktarımı konularını işleyeceğiz.



Modüllere Neden İhtiyacımız Var?

TypeScript’de yazılan kodlar, aksi belirtilmediği taktirde global scope seviyesinde tanımlanır. Peki bu ne anlama geliyor? TypeScript’de farklı dosyalarda bulunan kodlar, aslında alt alta yazılıyor gibi davranır. Dolayısıyla farklı dosyalar içerisinde bulunsalar dahi, derleme sırasında aynı isimde iki fonksiyon olması sebebiyle derleyici bize hata dönecektir. Bu sorunu hemen bir örnek üzerinde test ederek daha net anlayalım.


my-code.ts” isimli bir dosyayı aşağıdaki içerikle oluşturalım.

function myFunction(): void {

    console.log("Benim kodumdaki fonksiyon çalıştı");
}

Ardından “3rd-party-code.ts” isimli bir dosyayı aşağıdaki içerikle oluşturalım.

function myFunction(): void {

    console.log("3. parti koddaki fonksiyon çalıştı");
}

Tek seferde tüm dosyaları derleyebilmek için aynı klasörde “tsconfig.json” isimli dosyayı aşağıdaki içerik ile oluşturalım.

{
  "compilerOptions": {
    "target": "es2016",
    "module": "commonjs",
    "sourceMap": true,
    "esModuleInterop": true,
    "moduleResolution": "node",
    "forceConsistentCasingInFileNames": true,
    "strict": true
  }
}


Artık bu klasörde komut satırı veya terminal üzerinden “tsc” komutunu çalıştırarak projenin derlenmesini sağlayabiliriz. Ancak derleme sonrası aşağıdaki gibi bir hata ile karşılaşırız.


Aslında yazdığımız fonksiyonlar farklı dosyalarda olmasına rağmen sanki aynı dosyadaymış gibi birbirleri ile çakıştılar ve derleyici hata verdi.

Belki burada fonksiyonları ortaya yazmak yerine sınıflar içerisinde yazabilirdik, bu durumda fonksiyon isimleri çakışmazdı. Ancak bu sefer de sınıf isimleri çakışabilirdi. 10-15 dosyadan oluşan bir projede fonksiyon ve sınıf isimlerini kontrol altına almak kolay olabilir. Ancak ortalama büyüklükteki bir projede (kişisel tecrübeme göre) beş yüzden fazla TypeScript dosyası bulunacaktır. Bir de kullandığımız 3. parti kütüphanelerin kodlarını da hesaba katalım. Bu kadar dosya içerisinde aynı sınıf veya fonksiyon ismini kullanmak kaçınılmaz olacaktır.

Böyle bir karmaşadan kurtulmak için yazılan kodlar dosya bazında modüllere ayrılır ve her dosyanın bir diğerine doğrudan erişimi bulunmaz. Sadece kullanmamız gereken dosyaların içerisindeki kod parçalarını içeri aktararak (import) kullanırız.


Temel Modül Tanımı ve Kullanımı

Dışarı aktarım (export) ve içeri aktarım (import) konularını derinlemesine incelemeden önce, yukarıdaki örneğimizde iki farklı dosyada bulunan iki fonksiyonu nasıl export ve import ettiğimize bakarak konuya giriş yapmış olalım.

my-code.ts” isimli bir dosyanın içeriğini aşağıdaki şekilde değiştirelim.

export function myFunction(): void {

    console.log("Benim kodumdaki fonksiyon çalıştı");
}k

Yukarıdaki “export” ifadesi ile “myFunction” isimli fonksiyonu bu dosyadan dışarı aktarmış olduk. Böylece daha sonradan başka bir dosyada “import” ifadesi ile bu fonksiyonu içeri aktarabileceğiz.

3rd-party-code.ts” isimli bir dosyanın içeriğini de aşağıdaki şekilde değiştirelim.

export function myFunction(): void {

    console.log("3. parti koddaki fonksiyon çalıştı");
}


Benzer şekilde burada da “export” ifadesi ile “myFunction” isimli fonksiyonu bu dosyadan dışarı aktardık. Daha sonradan başka bir dosyada “import” ifadesi ile bu fonksiyonu da içeri aktarabileceğiz.

Daha da önemlisi, bu iki dosya içerisindeki fonksiyonların tanımı global scope seviyesinden çıkartılmış oldu. Yani özel olarak içeri aktarılmadığı sürece bu fonksiyonlara kimse ulaşamaz veya değiştiremez.

Son olarak bu iki dosyadaki fonksiyonları içeri aktararak kullanacak kodumuza gelelim. “main-code.ts” isimli bir dosyayı aşağıdaki içerikle oluşturalım.

import { myFunction } from './my-code';

myFunction(); // Benim kodumdaki fonksiyon çalıştı


Buradaki “import” ifadesi ile aynı klasörde bulunan “my-code.ts” dosyası içerisindeki “export” ifadesi ile dışarı aktarılmış olan “myFunction” isimli fonksiyonu içeri aktarmış olduk. Artık bu fonksiyonu sanki kendi dosyamızdaymış gibi kullanabiliriz (Ki kullandık).

Eğer ki 3. parti içerisindeki “myFunction” isimli fonksiyona erişmeye ihtiyacımız olsaydı, kodumuzu aşağıdaki gibi değiştirmemiz gerekecekti.

import { myFunction } from './3rd-party-code';

myFunction(); // 3. parti koddaki fonksiyon çalıştı


Peki ya dosyamızda her iki fonksiyona da erişmemiz gerekseydi ne yapacaktık? Bu durumda da kodumuzu aşağıdaki gibi değiştirmemiz gerekecekti.

import { myFunction as myCodeFunction } from './my-code';
import { myFunction as otherFunction } from './3rd-party-code';

myCodeFunction(); // Benim kodumdaki fonksiyon çalıştı

otherFunction(); // 3. parti koddaki fonksiyon çalıştı


Burada “import” ifadelerinde “as” ile fonksiyonlara takma ad (alias) vermiş olduk. Artık kodumuzda bu takma adlar üzerinden her iki fonksiyonu da çağırmamız mümkün olacaktır.


Dışarı ve İçeri Aktarımlarda Alternatif Seçenekler

Bir önceki başlıkta temel anlamda içeri ve dışarı aktarım işlemlerinin nasıl yapıldığını işledik. Şimdi aktarım işlemlerinin farklı seçeneklerine göz atacağız. Diyelim ki, “my-code.ts” dosyamızda “export” ile dışa aktardığımız daha fazla fonksiyon veya değer var. Dosyamızın içeriği aşağıdaki şekilde yazılmış diye varsayalım.

export const myValue1: string = "1. değişken";
export const myValue2: number = 7;

export function myFunction1(): void {

    console.log("myFunction1 çalıştı");
}

export function myFunction2(): void {

    console.log("myFunction2 çalıştı");
}

Burada “export” ile dışa aktardığımız iki fonksiyon ve iki değerimiz var. “main-code.ts” dosyamızda bunları “import” ile içe aktarıp kullanalım. Bu dosyamız da aşağıdaki şekilde yazılmış diye varsayalım.

import {myFunction1, myFunction2, myValue1, myValue2} from './my-code';

myFunction1();

myFunction2();

console.log(myValue1);
console.log(myValue2);

Bu kullanım şekli, genel kabul görmüş kullanım şeklidir. Ancak bazen hedef dosyamızda kullanmak isteyeceğimiz çok fazla sayıda bileşen olabilir. Bunları tek tek “import” ile içeri aktarmak yorucu olabilir. Bunun yerine “export” edilen tüm bileşenleri toplu olarak içeri aktarmak için aşağıdaki yöntemi de kullanabiliriz.

import * as myCode  from './my-code';

myCode.myFunction1();

myCode.myFunction2();

console.log(myCode.myValue1);
console.log(myCode.myValue2);

Böylece “*” ifadesi ile dışarı aktarılan tüm bileşenleri, verdiğimiz takma ad ile içeri aktarmış ve bu takma ad üzerinden bileşenlere ulaşmış oluruz.

Daha kompleks projeler yazdığımızda, çoğu zaman fonksiyonalite bir sınıf içerisinde bulunur ve fonksiyonları tek tek dışarı aktarmak yerine sınıfı tek seferde dışarı aktarırız. Böyle durumlarda varsayılan dışarı aktarım noktasını temsil etmesi için “default” sözcüğünü kullanırız. Aşağıdaki örneği inceleyelim.

export default class MyClass {

    myFunction1(): void {

        console.log("myFunction1 çalıştı");
    }

    myFunction2(): void {

        console.log("myFunction2 çalıştı");
    }
}

Burada tek bir dışarı aktarım noktamızı “default” sözcüğü ile varsayılan olarak işaretledik. Bunu diğer dosyamızda içeri aktarmak için yazmamız gereken kod aşağıdaki gibi olacaktır.

import MyClass from './my-code';

const myClass = new MyClass();

myClass.myFunction1(); // myFunction1 çalıştı
myClass.myFunction2(); // myFunction2 çalıştı

Bu yöntemle “import” ifadesi sonrasında “{}” işaretlerine gerek kalmadan seçtiğimiz bir isim ile içe aktarımı tamamlayıp sınıfı doğrudan kullanılabilir hale getirebiliriz.


3. Parti Kütüphanelerin Modül Olarak İçeri Aktarımı

Kendi yazdığımız dosyaların haricinde, başkalarının yazdığı ve kütüphane haline getirerek npm‘e yüklediği veya node.js’in doğrudan içerisinde bulunan diğer modülleri içeri aktarmak için de “import” ifadesini kullanırız. Buradaki “import” ifadesinin şimdiye kadar kullandıklarımızdan farkı, “from” ifadesinden sonra gelen hedef kısmıdır. Şimdiye kadar hedef olarak yazdığımız dosyanın bağıl yolunu gösterdik. “./” ifadesi bulunduğumuz klasörü temsil ediyordu. Ancak bahsettiğimiz 3. parti kütüphaneleri içeri aktarmak için sadece ismini yazmak yeterlidir.

Bu konuyu HTTP taleplerinden sorumlu popüler paketlerden biri olan axios üzerinden örneklendirelim. Öncelikle axios modülünü npm’den indirmek için terminalden aşağıdaki komutu çalıştıralım.

npm install axios

Ardından aşağıdaki gibi bir kod yazarak bu kütüphaneyi kullanalım ve verdiğimiz URL’deki içeriği çekip konsola yazdıralım.

import axios from "axios";

async function getPage(url: string): Promise<void> {

    const result = await axios.get(url);

    console.log(result);
}

getPage("https://www.leventkaragol.com")

Kod oldukça basit, açıklanacak pek bir tarafı yok. Burada dikkat etmemiz gereken yer “axios” yazılış şeklimiz olmalı. Diğer örneklerin aksine “./axios” şeklinde bağıl yol belirtmedik, direk kütüphanenin ismini yazdık. Böyle bir durumda ilgili kütüphane “node_modules” klasörü içerisinde aranacaktır.

Özetle TypeScript desteği olan tüm kütüphaneleri bu şekilde içeri aktararak kullanabiliriz.


TypeScript Desteği Olmayan Kütüphaneler için Deklarasyon Dosyalarının İçeri Aktarımı

Gelelim TypeScript desteği olmayan kütüphanelere. Bazen npm‘de bulduğumuz ve ihtiyacımız olan kütüphanelerin TypeScript desteği olmayabilir. Bu durumda ilgili kütüphanenin tip tanımlarının bulunduğu type kütüphanesini indirerek kütüphaneyi TypeScript’de kullanabiliriz.

Bu konuyu en popüler web framework olan express ile örnekleyelim. Öncelikle express modülünü npm’den indirmek için terminalden aşağıdaki komutu çalıştıralım.

npm install express

Ardından aşağıdaki gibi bir kod yazalım ve terminal üzerinden “tsc” komutu ile derlemeye çalışalım.

import express from "express";

const app = express();

app.get("/", function (request: any, response: any) {

    response.send("Test");
})

app.listen(3000);

Derleme sonrası aşağıdaki gibi bir hata alıyor olmalıyız.


Aslında hatada sorunun ne olduğu ve nasıl çözülebileceği de yazıyor. Diyor ki, express modülüne ait ve TypeScript için gerekli olan “.d.ts” uzantılı deklarasyon dosyaları bu modül içerisinde mevcut değilmiş. Bu yüzden TypeScript hangi fonksiyonun hangi parametreyi aldığı ve hangi değerleri döndüğüne dair bir bilgisi olmadığı için tip kontrolü de yapamıyormuş.

Ancak açıklamanın devamında diyor ki, gerekli olan bu deklarasyon dosyalarını indirmek için terminalde aşağıdaki komutu çalıştırabilirmişiz.

npm i --save-dev @types/express


Komutu çalıştırıp tekrar “tsc” ile kodu derlemeye çalıştığımızda bu kez sorunsuz derleniyor olması gerekli. Peki bu “@types” neyin nesi ki, TypeScript desteği olmayan bir kütüphaneye bir anda bu desteği kazandırdı?

Bunu anlamak için aslında npm‘e gidip “@types” diye aratmamız yeterli. Ben bu makaleyi yazarken 8.729 adet @types kütüphanesi mevcuttu. Bunlar, TypeScript desteklemeyen popüler kütüphanelerin deklarasyon dosyalarını barındıran kütüphaneler. Eğer orjinal kütüphanesinde tip tanımlarını içeren deklarasyon dosyaları bulunmayan bir kütüphanemiz varsa, bu kütüphane ile ilgili @types kütüphanesini de indirerek sorunsuzca kullanmaya devam edebiliriz.


Böylece TypeScript’de modüller konusunun sonuna geldik. Bir sonraki makalemizde TypeScript’de dekoratörler konusunu işleyeceğiz.

Herkese iyi çalışmalar…

İLGİNİZİ ÇEKEBİLİR

Bir Yorum Yaz