typescript - any, unknown, {} tipleri arasındaki ilişkiler ve bunların diğer tiplerle ilişkilerini nasıl anlamalıyız?
Türler arasındaki ilişkileri anlamaya çalışırken bu koda sahibim
type CheckIfExtends<A, B> = A extends B ? true : false;
type T1 = CheckIfExtends<number, unknown>; //true
type T2 = CheckIfExtends<number, {}>; //true
type T3 = CheckIfExtends<number, any>; //true
type T4 = CheckIfExtends<() => void, unknown>; //true
type T5 = CheckIfExtends<() => void, {}>; //true
type T6 = CheckIfExtends<() => void, any>; //true
type T7 = CheckIfExtends<unknown, any>; //true
type T8 = CheckIfExtends<any, unknown>; //true
type T9 = CheckIfExtends<{}, unknown>; //true
type T10 = CheckIfExtends<{}, any>; //true
type T11 = CheckIfExtends<any, {}>; //boolean
type T12 = CheckIfExtends<unknown, {}>; //false
Oyun alanına bağlantı
Biri bunu açıklayabilir mi? Hangi fark? any extends {} olurken ve aynı zamanda any {}'i extend etmezken bu nasıl mümkün olabilir? Eğer any extends unknown ve unknown extends any ise, bu onların güçlü bir şekilde eşit olduğu anlamına mı geliyor? Bu, JavaScript'in null ve undefined eşitlik sorunlarının üstüne Typescript'in yeni bir hatası mı?
Aslında,
type T = CheckIfExtends<any, number>; //boolean
Cevap #1
Ayrım esasen şudur:
anytürü kasıtlı olarak güvenilmezdir; çünkü hem herhangi bir türden hem de herhangi bir türe atanabilir (nerede kullandığınıza bağlı olarakneveristisna olabilir). Güvenilmezlik, alt türlerin geçişliliği gibi türler için bazı temel kuralların bozulduğu anlamına gelir. Genel olarak, eğerAB'ye atanabiliyorsa veBC'ye atanabiliyorsa, o zamanAC'ye atanabilir. Ancakanybunu bozar. Örneğin:string,any'ye atanabilir veany,number'a atanabilir... amastring,number'a atanamaz. Bu özel güvenilmezlik çok kullanışlıdır, çünkü temelde tür denetimini doğru bir şekilde yazılması zor veya imkansız olan bir kod parçasında "kapamamıza" izin verir. Amaany'yi bir tür olarak düşünürken çok dikkatli olmalısınız; bu daha çok bir "typesız" gibidir.boş tür,
{}, çalışma zamanında bir nesne gibi davranılabilen bir türdür (yani, özelliklerini veya yöntemlerini çalışma zamanında hata almadan okuyabileceğiniz bir şeydir), ancak derleme zamanında bilinen hiçbir özelliği yoktur. Bu, özellikleri olmadığı anlamına gelmez; sadece derleyicinin bunlardan herhangi birini bilmediği anlamına gelir. Bu, yalnızcanullveundefineddeğerlerinin{}türüne atanamayacağı anlamına gelir (null.fooveyaundefined.fooçalışma zamanı hatasıdır).stringgibi ilkel türler bile çalışma zamanında özellikler ve yöntemler varmış gibi davranılabilir ("".lengthve"".toUpperCase()çalışır, hatta"".foosadeceundefineddöner). Ve elbette herhangi bir gerçek nesne türü de{}türüne atanabilir.Öte yandan,
{}türü çok fazla türe atanamaz. Eğer elimde{}türünde bir değer varsa ve bunu{foo: string}türündeki bir değişkene atamaya çalışırsam, derleyici hatası alırım, çünkü{}içinde birfooözelliği bulunduğu bilinmemektedir.{}kendisine veyaunknowngibi daha geniş bir türe, ya da "türü olmayan"any'ye atanabilir.Bu,
{}'yi tüm diğer türlerin atanabileceği bir tür olan neredeyse bir üst tür yapar. Temeldenullveundefinedçıkarılmış bir üst türdür.unknowntürü TypeScript 3.0'da tanıtıldı ve gerçek üst türdür; TypeScript'teki her türunknown'a atanabilir. Hattanullveundefinedbileunknown'a atanabilir.Öte yandan,
unknownyalnızca kendisine ve "tipsiz"any'ye atanabilir. Hatta{}tipi bileunknown'u atamak için yeterince geniş değildir. Bunun yerine,unknownesasen{} | null | undefinedile eşdeğerdir (ve TypeScript 4.8'den itibaren böyle görülür).
Çoğu CheckIfExtends<A, B> sonucunuz yukarıdakilerle açıklanabilir. İstisna T11:
type T11 = CheckIfExtends<any, {}>; //boolean
CheckIfExtends<A, B> tür tanımınız dağıtılabilir bir koşullu türdür ve A bir birleşim türü olduğunda ilginç şeyler yapar; çünkü birleşimin parçaları her iki dalı da sağlıyorsa koşullu ifadenin her iki dalının da alınmasına izin verir. Ayrıca A any olduğunda da aynı dağıtımı yapar, B any veya unknown olmadıkça (bu nedenle T8 normal davranır). Bununla ilgili bazı tartışmalar microsoft/TypeScript#27418 numaralı konuda mevcuttur. Her neyse, T11 her iki dalı da alır ve bu yüzden true | false elde edilir, bu da boolean olur. (microsoft/TypeScript#27418'den; A konumundaki unknown dağıtılmaz, bu nedenle T7 ve T12 de normal davranır.)
Cevap #2
TypeScript’te extends ifadesinin ne anlama geldiğinden başlayacağım. İlk bakışta garip görünüyor; ürün türleri (nesneler gibi) için 'üstkümesi mi' gibi davranırken, birleşimler için 'altkümesi mi' gibi davranıyor ve fonksiyon türleri için tamamen farklı çalışıyor. İlk başta garip görünebilir, ama mantıklı bir davranış, başka bir deyişle çoğu türün sağlamlık (soundness) özelliği vardır.
Bu kavramı anlamak için genel kuralım, extends öğesini atanabilir olarak okumaktır. Eğer x y öğesini genişletiyorsa, bu y gerektiğinde x'in kullanılabileceği anlamına gelir.
Eğer yukarıdakiler onlar için geçerliyse, üç farklı cebirsel veri tipini ele alalım.
Ürün türü için
type A = {a: string}
type B = {a: string; b: number}
type BextendsA = B extends A ? true : false // evaluates to true
Yukarıdaki doğru çünkü B, A'nın gerektiği her yerde kullanılabilir, çünkü B, A'nın sahip olduğu tüm yapıyı kapsar. B, A'nın bir üst kümesidir. Ama burada geçerli olan, B'nin A'ya atanabilir olmasıdır.
Birleşim türü için
type A = number | string
type B = number
type BextendsA = B extends A ? true : false // evaluates to true
Birleşim için tamamen farklı görünüyor. Ürün için B bir üst küme idi, birleşim için B bir alt küme! Evet, farklı mantık, B A'daki tüm olası değerleri temsil etmiyor, ama her ne zaman A gerekliyse B onun yerine kullanılabilir. Yani B, A'ya atanabilir.
Fonksiyon türleri için
type A = (a: number) => void
type B = () => void
type BextendsA = B extends A ? true : false // evaluates to true
Fonksiyon tipleri için, durum daha da garip görünüyor çünkü A, B'den daha belirli bir fonksiyon gibi görünüyor, o zaman B nasıl A'yı genişletebilir? Bu tekrar atama yeteneğinden kaynaklanıyor, çünkü bir şey A'ya ihtiyaç duyduğunda biz B'yi atayabiliriz. Bu, Array.map gibi örneklerde çok belirgindir. Düşünün:
[1,2].map(x => x + 1)
Array.map, üç argümanı olan bir fonksiyon gerektirir - (el,index,arr) => any ancak sadece bir argümana sahip bir fonksiyonla da çalışabilir el => any. Ve yine atanabilirlik geçerlidir, B A'ya atanabilir.
any, unknown, {} gibi türler güvenilmezdir, bu onların davranışlarının mantıksal olarak kanıtlanamayacağı anlamına gelir. Bunların TS'de nasıl davrandığını anlamak, daha çok spesifikasyonu anlamak ve bu tür kararların sebeplerini tartışmak demektir. Ama mantıksal olarak açıklanamazlar, çünkü güvenilmez türler mantığa aykırı davranır.
TypeScript'in güvenli olmayan davranışa izin verdiği yerler dikkatlice değerlendirildi ve bu belgede bu durumların nerede meydana geldiğini ve bunların ardındaki motivasyonel senaryoları açıklayacağız.
