TypeScript 2.8 Conditional Types
Selamlar,
Bugün TypeScript 2.8 ile gelicek “Conditional Types” konusunu inceleyeceğiz. Typescript 2.8 ile gelen bu özellik, nesnenin tipini belirlenen bir koşula göre atamaktadır.
T extends A ? B : C
Kısaca yukarıda görüldüğü gibi, gelen T tipindeki nesne, A tipine atanabiliyor ise “B” aksi halde “C” tipinde demektir.
Basit bir örnek: Aşağıda görüldüğü gibi T tipinin sayısal olmasına göre bir koşullama yapılmış ve “true”, “false” şeklinde bir atama yapılmıştır.
1 2 3 4 |
type IsNumberType<T> = T extends number ? "true" : "false"; type Yes = IsNumberType<1453>; // type "true" type No = IsNumberType<"arya">; // type "false" |
Bu koşulları çoğaltmak da mümkündür: Örneğin aşağıda görüldüğü 2’den fazla koşul için tip tanımlama yapılabilir. Eğer en son koşula bakarsanız, “T” tipi “function” değil ise default olarak “object” tipi atanmaktadır.
1 2 3 4 5 6 7 8 |
type TypeName<T> = T extends string ? "string" : T extends number ? "number" : T extends boolean ? "boolean" : T extends date ? "date" : T extends <span class="hljs-literal">undefined</span> ? <span class="hljs-string">"undefined"</span> : T extends Function ? "function" : "object"; |
Hadi gelin yukarıdaki koşullamayı örnekler ile inceleyelim:
- Aşağıdaki örnekde, string gelen “a”‘ya karşılık T1’e “string” tipi atanmıştır.
- “true” şeklinde gelen boolean tipine karşılık, T2’ye yine “boolean” tipi atanmıştır.
- ‘30.03.2018’ tarih tipine karşılık, T3’e “date” tipi atanmıştır.
- ()=>void function tipine karşılık, T4’e “function” tipi atanmıştır.
- Yukarıdaki tanımlamalar arasında olmayan string [ ] tipine karşılık T5’e “object” tipi atanmıştır.
1 2 3 4 5 |
type T1 = TypeName<"a">; //"string" type T2 = TypeName<true>; //"boolean" type T3 = TypeName<'30.03.2018'>; //"date" type T4 = TypeName<() => void>; //"function" type T5 = TypeName<string[]>; //"object" |
Eski yöntemler ile bu işlemi aşağıdaki gibi “switch-case” yapısı ile çözebilirdik :)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
type EmptyOf<T extends number> = 0; type EmptyOf<T extends string> = ""; type EmptyOf<T extends boolean> = false; type EmptyOf<T> = never; function emptyOf(readonly val: number | string | boolean): EmptyOf<typeof val> { switch (typeof val) { case "number": return 0; case "string": return ""; case "boolean": return false; default: throw new TypeError("Beklenmeyen bir Null Hatası!"); } } |
Union Types
Sırada aynı değişken için, birden çok farklı tipin bakılmasına geldi:
1 2 3 |
type T10 = TypeName<boolean | (() => void)>; //"boolean" | "function" type T11 = TypeName<string | number[] | undefined>; //"string" | "object" | "undefined" type T12 = TypeName<string[] | number[]>; //"object" |
Yukarıdaki örneklere baktığımızda:
- T10’da makalenin başındaki tanımlamada karşılığı olan, boolean ya da function tipinde olabilir.
- T11 için string ve undfined’in, makalenin başındaki tanımlamada bir karşılığı olmasına rağmen, number [ ] tipinin bir karşılığı olmadığı için bu tipde gelen değer “object” olarak tanımlanmıştır.
- T12 hem “string[ ]” hem de “number [ ]” tiplerinin bir karşılığı olmadığı için, bu tiplerde gelen değerler “object” olarak tanımlanmıştır.
Aşağıdaki örnekte “CustomValue” ve “CustomArray” tipleri tanımlanmıştır. Convert tipine, bu tanımlı tiplerden biri, gelen değişken tipine göre atanabilmektedir. Yani bir özel tipe yine tanımlı başka özel bir tip atanabilmektedir.
1 2 3 |
type CustomValue<T> = { value: T }; type CustomArray<T> = { array: T[] }; type Convert<T> = T extends any[] ? CustomArray<T[number]> : CustomValue<T>; |
Aşağıda T20’ye gelen “date” tipine biri dizi olmadığı sadece bir value olduğu için “CustomValue<date>” atanmıştır. T21 tipi için ise bir “string [ ]” değer geldiği için “CustomArray<string>” atanmıştır. Son olarak T22 için “Convert<string | number[ ]>” ile T22’ye ya string value “CustomValue<string>” ya da number of array [ ] yani “ConvertArray<number>” atanabilmektedir.
1 2 3 |
type T20 = Convert<date>; //CustomValue<date>; type T21 = Convert<string[]>; //CustomArray<string>; type T22 = Convert<string | number[]>; //CustomValue<string> | CustomArray<number>; |
Eski sistemde Union Typelar, aşağıdaki gibi ele alınıyordu:
1 2 3 4 5 6 7 8 9 10 11 |
function padLeft(value: string, padding: any) { if (typeof padding === "number") { return Array(padding + 1).join(" ") + value; } if (typeof padding === "string") { return padding + value; } throw new Error(`Expected string or number, got '${padding}'.`); } padLeft("Hello world", 4); // returns " Hello world" |
Aşağıda görüldüğü gibi custom Filter ve Diff tiplerine . “Diff” tipinde “never” ile T’nin U’dan atanmışlar dışındakileri atanır. Kısaca T’ye, U’ya atanmayanlar atanır. “Filter” tipinde T’ye U’dan atanmamış tipler çıkarılır. Kısaca T’ye de, U’ya atanmış tipler atanır.
1 2 |
type Diff<T, U> = T extends U ? never : T; type Filter<T, U> = T extends U ? T : never; |
Şimdi sıra geldi 2 farklı or “|” ile yazılmış çoklu tip tanımlamada benzer ve farklı olanları custom “Filter” ve “Diff” ile bulmaya.
Aşağıda görüldüğü gibi T23’de 1. or’lu “Diff” tanımlamada “1,2,3,4”‘ün ==> “1.3.5”‘den farklı olanları 2 ve 4’dür. T24’de 1. ve 2. or’lu “Filter” tanımlamada aynı olanlar “a” ve “c”‘dir.
1 2 |
type T23 = Diff<"1" | "2" | "3" | "4", "1" | "3" | "5">; // "2" | "4" type T24 = Filter<"a" | "b" | "c" | "d", "a" | "c" | "f">; // "a" | "c" |
NonNullable Type Operator : Aşağıda görüldüğü gibi T, null ve undefined’dan farklı olucak şeklinde bir tanımlama yapılmıştır. Name, atanan tiplerden sadece “string”‘e uymaktadır.
1 2 |
type NonNullable<T> = Diff<T, null | undefined>; type Name = NonNullable<string | null | undefined>; // Name = string |
Predefined conditional types: Typescript 2.8’de “lib.d.ts” ile hazır olarak birçok predefined conditional tipler gelmektedir.
- Exclude<T, U> :T’nin U’dan farklı olan “b” ve “c” değerleri T1’e atanabilir.
1 |
type T1 = Exclude<"a" | "b" | "c" | "d", "a" | "e" | "d">; // "b" | "c" |
- Extract<T, U> : T’nin U’dan türüyen tipleri “a” ve “d” T2’ye atanabilir.
1 |
type T2 = Extract<"a" | "b" | "c" | "d", "a" | "e" | "d">; // "a" | "d" |
- NonNullable<T> : “null” ve “undefined” T’den çıkartlır. Aşağıda T3 için function (()=>string) ve number [ ] tipleri dönülür.
1 |
type T3 = NonNullable<(() => string) | number[] | null | undefined>; // (() => string) | number[] |
Aşağıdakileri de siz araştırabilirsiniz:)
- ReturnType<T>
- InstanceType<T>
Örnek : Şimdi gelin isterseniz Conditional Typelar ile biraz beyin jimnastiği yapalım:
- Id interface’i id ==> number ve Name interface’i name ==> string propertyleri içermektedir.
- createLabel function’ı gelen tipe göre Id veya Name function’ı dönmektedir.
1 2 3 4 5 |
interface Id { id: number, /* diğer alanlar */ } interface Name { name: string, /* diğer alanlar */ } declare function createLabel(id: number): Id; declare function createLabel(name: string): Name; |
3. Aşağıdaki örneğe göre, IdOrName tipi “number” veya “string” tipi olabilir. Eğer number ise ==>”Id” inerface’i, değil ise yani string ise “Name” interface’i döner.
4. Aşağıdaki örneğe göre, createLabel() function’ı number veya string tipinde bir “T” parametresi olmak üzere “idOrName” tipinde bir değişken almaktadır.
1 2 3 4 |
type IdOrName<T extends number | string> = T extends number ? Id : Name; declare function createLabel<T extends number | string>(idOrName: T): IdOrName<T>; |
5. a,b,c,d değişkenlerinin değerleri aşağıdaki örnekde olduğu gibi:
- “a”‘ya ==> “duru” ==> “string” olduğu için IdOrName tipine göre “Name” tipi almıştır.
- “b”‘ye ==> 26 ==> number olduğu için “IdOrName” tipine göre “Id” atanmıştır.
- “c”‘ye ==> ” ” any atandığı için “IdOrName” tipine göre ya “Id” ya da “Name” atanabilmektedir.
- “d”‘ye ==> ” ” never atandığı için “IdOrName” tipine göre “never” atanmıştır.
1 2 3 4 |
let a = createLabel("duru"); // Name let b = createLabel(26); // Id let c = createLabel("" as any); // Id | Name let d = createLabel("" as never); // never |
Typescript 2.8 Conditional Typelar ile, hayatımıza kod sadeliğinin ve daha anlaşılır bir yapının girmesi amaçlanmıştır. Günümüzde hali hazırda kullanılan bir çok design patternde örneğin, Creational Design Patternlerden Builder gibi yapılarda bu yöntemin kullanılması, daha basit çözümlere gidilebilmesine olanak sağlıyacaktır.
Geldik bir makalenin daha sonuna. Yeni bir makalede görüşmek üzere hepinize hoşçakalın.
Kaynak: https://blogs.msdn.microsoft.com/typescript/2018/03/27/announcing-typescript-2-8/
Cok guzel bir makale. Ufak bir sorum olucak. C#’larda generic method yazarken genelde
GetSomethingByCategory(T model, …) diyerek kullanabiliyoruz. T’ye ister concrete class, ister primitive type, ister IEnumerable collection …etc atayabiliyoruz bunu Typescript’te yapmak mumkun mu?
Eline saglik Bora abi. Guzel bir ozellik. Denemek gerekiyor :)
@Elif Ayse
Eger asagidakini soruyor isen mumkun (umarim pre ve code etiketleri yorum kisminda aktiftir)
Bora abi öncelikle faydalı bilgiler sunduğun için teşekkürler. Benim sorum Angular ile ilgili olacak. Angular 4 ile proje geliştirmeye başladım. Arayüzü için hazır css templateleri kullanmak istiyorum fakat css dosyalarında url ile resim yolları belirtilmiş. Çalıştırdığımda böyle bir modül yok şeklinde hata alıyorum bunun önüne nasıl geçerim bir önerin var mıdır ?. Teşekkürler
Selam Melih,
Örnek bir kod ve ekran ya da hata çıktısı paylaşırsan sevinirim. Muhtemelen contentlerin yolu yanlış..
İyi çalışmalar.
İlginiz için teşekkür ederim bora abi. Sorunu çözdüm teşekkürler.