Ders 16 / 25
Özel Kancalar ve Birleştirilebilir İşlevler
Durumu ve yan etkiyi işaretleme üretmeden paketleyen birimler; konum tabanlı kancaların çağrı sırası kuralı, kurulumda bir kez çalışan işlevlerin kapsam temizliği ve iki modelin karşılaştırması.
İçindekiler
Önceki ders yapının yeniden kullanımını çözdü. Ancak bileşenlerde tekrar eden tek şey yapı değildir. Kuzey Yamaç Ölçüm İstasyonu sayfasında süzgeç panelinin arama alanı girdiyi geciktirerek tetikliyor, ölçüm tablosu sıralama tercihini tutuyor, ölçüm rozeti aralıklı olarak yenileniyor. Üçünde de durum var, üçünde de bir yan etki ve o etkinin temizliği var. Üçü de ayrı ayrı yazılmış.
Bu mantık bileşenin gövdesinde durduğu sürece taşınamaz. Taşınabilmesi için işaretlemeden ayrılması, ayrılabilmesi içinse durumun bir bileşene değil bir çağrıya bağlanabilmesi gerekir. Bu dersin konusu, bu çağrının iki farklı çerçeve ailesinde nasıl kurulduğu ve her birinin hangi kuralı dayattığıdır.
Ayrılan Nedir
Bir bileşen iki iş yapar: durumu yönetir ve o durumdan bir ağaç üretir. İkinci iş işaretlemedir ve bileşene özgüdür. Birinci iş çoğu zaman özgü değildir.
Ayırma işlemi şu biçimi alır: durumu, türetilmiş değerleri ve yan etkileri tutan; işaretleme üretmeyen; dışarıya okunacak değerlerle çağrılacak işlevlerden oluşan bir tutamak döndüren bir işlev. Sanal ağaç tutan çerçeve ailesinde bu işleve özel kanca (custom hook), bağımlılığı çalışma zamanında izleyen ailede birleştirilebilir işlev (composable) denir. Amaçları aynı, çalışma zamanı sözleşmeleri farklıdır.
Ayrımın sınavı basit bir sorudur: bu işlevi çağıran iki bileşen tümüyle farklı görünebiliyor mu? Yanıt hayırsa ayrılan şey mantık değil, gizlenmiş bir bileşendir.
Konuma Bağlı Yuvalar
Sanal ağaç ailesinde bileşen bir işlevdir ve her çizimde baştan çalışır. Bir çizimde oluşturulan yerel değişkenler sonraki çizime kalmaz. Durumun bileşenin dışında, çerçevenin tuttuğu bir yerde yaşaması gerekir.
Bunun en ucuz gerçekleştirimi konumdur: bileşene ait bir yuva dizisi tutulur, her kanca çağrısı bir sonraki yuvayı alır. Kancanın kimliği adı ya da parametresi değil, kaçıncı çağrı olduğudur.
// kanca.mjs — konum tabanli kanca calisma zamani, kanca bilesimi ve cagri sirasi kurali let yuvalar = []; let imlec = 0; // Bir cizim: imleci sifirla, bilesen islevini calistir. const ciz = (bilesen, ozellikler = {}) => { imlec = 0; return bilesen(ozellikler); }; // Tek ilkel: konuma bagli durum yuvasi. function durumKullan(ilkDeger) { const i = imlec++; if (!(i in yuvalar)) yuvalar[i] = ilkDeger; return [yuvalar[i], (yeni) => { yuvalar[i] = yeni; }]; } // Ozel kanca: iki yuva ve bir dogrulama kurali; isaretleme uretmez. function esikKullan(baslangic) { const [esik, esikAyarla] = durumKullan(baslangic); const [dokunuldu, dokunulduAyarla] = durumKullan(false); const gecerli = Number.isFinite(esik) && esik >= -40 && esik <= 40; return { esik, gecerli, hata: dokunuldu && !gecerli ? "eşik -40 ile 40 arasında olmalı" : null, yaz(yeni) { esikAyarla(yeni); dokunulduAyarla(true); }, }; } // Ikinci ozel kanca: siralama alani ve yonu. function siralamaKullan(ilkAlan) { const [alan, alanAyarla] = durumKullan(ilkAlan); const [artan, artanAyarla] = durumKullan(true); return { alan, artan, tikla(yeni) { if (yeni === alan) artanAyarla(!artan); else { alanAyarla(yeni); artanAyarla(true); } }, }; } // Ucuncu ozel kanca yalnizca ilk ikisini birlestirir; kendi yuvasi yoktur. const suzgecKullan = () => ({ esik: esikKullan(-5), siralama: siralamaKullan("ad") }); // Bilesen: kancayi cagirir, gorunumunu ve disariya verdigi tutamagi dondurur. function suzgecPaneli() { const s = suzgecKullan(); const ozet = `eşik=${s.esik.esik} geçerli=${s.esik.gecerli} hata=${s.esik.hata ?? "-"} ` + `sıralama=${s.siralama.alan}/${s.siralama.artan ? "artan" : "azalan"}`; return { ozet, s }; } console.log("--- iki ozel kancanin bilesimi: dort yuva ---"); const c1 = ciz(suzgecPaneli); console.log("1. çizim:", c1.ozet, "| yuvalar:", JSON.stringify(yuvalar)); c1.s.esik.yaz(88); const c2 = ciz(suzgecPaneli); console.log("2. çizim:", c2.ozet, "| yuvalar:", JSON.stringify(yuvalar)); c2.s.siralama.tikla("deger"); const c3 = ciz(suzgecPaneli); console.log("3. çizim:", c3.ozet, "| yuvalar:", JSON.stringify(yuvalar)); c3.s.siralama.tikla("deger"); const c4 = ciz(suzgecPaneli); console.log("4. çizim:", c4.ozet, "| yuvalar:", JSON.stringify(yuvalar)); // --- Cagri sirasi kurali --- console.log("\n--- kosullu cagri: yuvalarin kaymasi ---"); yuvalar = []; function bozukPanel({ gelismis }) { const [sorgu] = durumKullan("kar"); if (gelismis) durumKullan(-5); // yalnizca bazi cizimlerde cagriliyor const [siralama] = durumKullan("ad"); return { sorgu, siralama }; } console.log("gelişmiş açık :", JSON.stringify(ciz(bozukPanel, { gelismis: true })), "| yuvalar:", JSON.stringify(yuvalar)); console.log("gelişmiş kapalı:", JSON.stringify(ciz(bozukPanel, { gelismis: false })), "| yuvalar:", JSON.stringify(yuvalar)); // Kurali uygulayan surum: kanca kosulsuz cagrilir, kosul dondurulen degerdedir. console.log("\n--- kosulsuz cagri, kosullu deger ---"); yuvalar = []; function saglamPanel({ gelismis }) { const [sorgu] = durumKullan("kar"); const [esik] = durumKullan(-5); const [siralama] = durumKullan("ad"); return { sorgu, esik: gelismis ? esik : null, siralama }; } console.log("gelişmiş açık :", JSON.stringify(ciz(saglamPanel, { gelismis: true })), "| yuvalar:", JSON.stringify(yuvalar)); console.log("gelişmiş kapalı:", JSON.stringify(ciz(saglamPanel, { gelismis: false })), "| yuvalar:", JSON.stringify(yuvalar));
--- iki ozel kancanin bilesimi: dort yuva ---
1. çizim: eşik=-5 geçerli=true hata=- sıralama=ad/artan | yuvalar: [-5,false,"ad",true]
2. çizim: eşik=88 geçerli=false hata=eşik -40 ile 40 arasında olmalı sıralama=ad/artan | yuvalar: [88,true,"ad",true]
3. çizim: eşik=88 geçerli=false hata=eşik -40 ile 40 arasında olmalı sıralama=deger/artan | yuvalar: [88,true,"deger",true]
4. çizim: eşik=88 geçerli=false hata=eşik -40 ile 40 arasında olmalı sıralama=deger/azalan | yuvalar: [88,true,"deger",false]
--- kosullu cagri: yuvalarin kaymasi ---
gelişmiş açık : {"sorgu":"kar","siralama":"ad"} | yuvalar: ["kar",-5,"ad"]
gelişmiş kapalı: {"sorgu":"kar","siralama":-5} | yuvalar: ["kar",-5,"ad"]
--- kosulsuz cagri, kosullu deger ---
gelişmiş açık : {"sorgu":"kar","esik":-5,"siralama":"ad"} | yuvalar: ["kar",-5,"ad"]
gelişmiş kapalı: {"sorgu":"kar","esik":null,"siralama":"ad"} | yuvalar: ["kar",-5,"ad"]
İlk bölüm bileşimin çalıştığını gösteriyor. suzgecKullan kendi yuvası olmayan bir
kancadır; yalnızca iki kancayı yan yana çağırır ve dördüncü çizime kadar dört yuva sırayla
doğru değerleri taşır. Yuva dizisi düz bir listedir: iç içe kanca çağrılarının hiyerarşisi
dizide görünmez, yalnızca çağrı sırası görünür.
Kancaların birbirini çağırabilmesi bu modelin gücüdür. Bir kanca içeride kaç yuva kullandığını dışarıya bildirmez; çağıranın bilmesi de gerekmez, yeter ki sıra sabit kalsın.
Çağrı Sırası Kuralı
İkinci bölüm sabitliğin bozulduğunda ne olduğunu gösteriyor. gelismis doğruyken üç kanca
çağrılıyor ve yuvalar ["kar", -5, "ad"] oluyor. Aynı bileşen gelismis yanlışken
çizildiğinde ortadaki çağrı atlanıyor; sıralama kancası artık ikinci sırada ve birinci
kancanın yuvası olan 1. dizini okuyor. Sonuç, sıralama alanının -5 sayısı olması.
Hata sessizdir. Bir istisna atılmaz, bir uyarı yazılmaz; yalnızca yanlış değer okunur. Ölçüm tablosunda bu, sıralamanın sessizce bozulması demektir.
Buradan konum tabanlı çalışma zamanının tek kuralı çıkar: kancalar her çizimde aynı sayıda ve aynı sırada çağrılmalıdır. Koşula, döngüye ya da erken dönüşe konamazlar. Üçüncü bölüm kuralın nasıl uygulandığını gösteriyor: kanca koşulsuz çağrılır, koşul döndürülen değere taşınır. Yuva dizisi her iki çizimde de aynı kalıyor.
Kuralın bir bedeli var: kullanılmayan durum yine de tutulur. gelismis kapalıyken eşik
yuvası bellekte durur ve değerini korur. Bu, çoğu zaman istenen davranıştır — kullanıcı
gelişmiş paneli kapatıp açtığında eşik yerinde durur — ama bir tercih değil, modelin
zorunlu sonucudur.
Kurulumda Bir Kez Çalışan İşlevler
Bağımlılığı çalışma zamanında izleyen ailede bileşen işlevi her güncellemede yeniden çalışmaz; bir kez çalışır, tepkisel değerleri kurar ve görünümü bu değerlere bağlar. Durumun kimliği bir yuva sırası değil, kurulumda üretilen nesnenin kendisidir.
// birlestirilebilir.mjs — kurulum sirasinda bir kez calisan islevler ve kapsam temizligi let etkinKapsam = null; function kapsamAc(kurulum) { const temizlikler = []; const onceki = etkinKapsam; etkinKapsam = { temizlikEkle: (f) => temizlikler.push(f) }; const sonuc = kurulum(); etkinKapsam = onceki; return { sonuc, yikil() { // son kurulan ilk yikilir for (let i = temizlikler.length - 1; i >= 0; i--) temizlikler[i](); temizlikler.length = 0; }, bekleyenTemizlik: () => temizlikler.length, }; } // Konumdan bagimsiz durum: kimlik yuvanin sirasi degil, nesnenin kendisidir. function durum(ilkDeger) { let deger = ilkDeger; const dinleyiciler = new Set(); return { oku: () => deger, yaz(yeni) { deger = yeni; for (const d of [...dinleyiciler]) d(yeni); }, dinle(d) { dinleyiciler.add(d); etkinKapsam?.temizlikEkle(() => dinleyiciler.delete(d)); }, dinleyiciSayisi: () => dinleyiciler.size, }; } const kutuk = []; // Birlestirilebilir islev 1: esik ve dogrulamasi. function esikKullan(baslangic) { const esik = durum(baslangic); const gecerli = () => Number.isFinite(esik.oku()) && esik.oku() >= -40 && esik.oku() <= 40; esik.dinle((v) => kutuk.push(`eşik yazıldı: ${v} (geçerli=${gecerli()})`)); return { esik, gecerli }; } // Birlestirilebilir islev 2: sahte zamanlayiciyla aralikli yenileme. const saat = { gorevler: [], her: (ad, is) => { saat.gorevler.push({ ad, is }); return () => { saat.gorevler = saat.gorevler.filter((g) => g.is !== is); }; }, tik: () => saat.gorevler.forEach((g) => g.is()) }; function yenilemeKullan(ad) { const sayac = durum(0); const durdur = saat.her(ad, () => sayac.yaz(sayac.oku() + 1)); etkinKapsam?.temizlikEkle(() => { durdur(); kutuk.push(`${ad}: yenileme durduruldu`); }); return sayac; } // Birlestirilebilir islev 3: yalnizca digerlerini birlestirir. function panelKurulumu({ gelismis }) { const esikDurumu = esikKullan(-5); // Kosullu kurulum guvenlidir: kimlik konuma bagli degil. const yenileme = gelismis ? yenilemeKullan("gelişmiş panel") : null; return { ...esikDurumu, yenileme }; } console.log("--- gelismis kapali ---"); const a = kapsamAc(() => panelKurulumu({ gelismis: false })); a.sonuc.esik.yaz(12); saat.tik(); console.log("yenileme nesnesi:", a.sonuc.yenileme, "| saatteki görev:", saat.gorevler.length, "| bekleyen temizlik:", a.bekleyenTemizlik()); console.log("\n--- gelismis acik ---"); const b = kapsamAc(() => panelKurulumu({ gelismis: true })); b.sonuc.esik.yaz(88); saat.tik(); saat.tik(); console.log("yenileme sayacı:", b.sonuc.yenileme.oku(), "| saatteki görev:", saat.gorevler.length, "| bekleyen temizlik:", b.bekleyenTemizlik()); console.log("eşik dinleyicisi:", b.sonuc.esik.dinleyiciSayisi()); console.log("\n--- kapsam yikildiktan sonra ---"); b.yikil(); saat.tik(); console.log("saatteki görev:", saat.gorevler.length, "| eşik dinleyicisi:", b.sonuc.esik.dinleyiciSayisi(), "| yenileme sayacı:", b.sonuc.yenileme.oku()); console.log("\nkütük:"); for (const s of kutuk) console.log(" ", s);
--- gelismis kapali --- yenileme nesnesi: null | saatteki görev: 0 | bekleyen temizlik: 1 --- gelismis acik --- yenileme sayacı: 2 | saatteki görev: 1 | bekleyen temizlik: 2 eşik dinleyicisi: 1 --- kapsam yikildiktan sonra --- saatteki görev: 0 | eşik dinleyicisi: 0 | yenileme sayacı: 2 kütük: eşik yazıldı: 12 (geçerli=true) eşik yazıldı: 88 (geçerli=false) gelişmiş panel: yenileme durduruldu
Koşullu kurulum burada güvenlidir. gelismis kapalıyken yenileme işlevi hiç çağrılmaz,
hiçbir yuva kaymaz, hiçbir değer bozulmaz — çünkü kimlik sıraya değil nesneye bağlıdır.
Kapalı durumda saatte hiç görev yoktur; açık durumda bir görev vardır ve iki tikten sonra
sayaç 2’dir.
İkinci ayrım temizliktedir. Birleştirilebilir işlevler kendi temizliklerini kurulum sırasındaki etkin kapsama kaydeder; çağıran bunu bilmez ve elle bir şey yapmaz. Kapsam yıkıldığında kayıtlar ters sırada çalışır: son kurulan ilk yıkılır. Yıkımdan sonra saatte görev, eşikte dinleyici kalmaz; sayaç son değerinde donar.
Ters sıra bir ayrıntı değildir. İkinci işlev birincinin kurduğu şeye yaslanıyorsa, yıkım sırasında birinci hâlâ ayakta olmalıdır. Kurulum sırasının tersi bu koşulu her zaman sağlar.
İki Modelin Ödünleşimi
İki model aynı amacı farklı bedellerle karşılar.
Konum tabanlı model çalışma zamanında hiçbir defter tutmaz: yuva dizisi ve bir imleç yeter. Bedeli, çağrı sırasının bir sözleşmeye dönüşmesi ve bu sözleşmenin dilin kendisi tarafından denetlenememesidir. Kural, insan disiplinine ve statik çözümlemeye bırakılır.
Kurulum tabanlı model çağrı sırasına hiç bağımlı değildir; koşullu, döngü içinde, gecikmeli kurulum yapılabilir. Bedeli, her tepkisel değerin ve her dinleyicinin bir nesne olarak tutulmasıdır: kayıt defteri çalışma zamanındadır. Ayrıca “kurulum bir kez çalışır” kuralı kendi tuzağını getirir — kurulumda okunan bir özellik, o özelliğin ilk değerini yakalar; sonraki değerleri izlemek için okunan değerin kendisinin tepkisel olması gerekir.
Her iki modelde de üç kural ortaktır. Ayrılan işlev işaretleme döndürmez; döndürseydi o bir bileşen olurdu. Aldığı ve döndürdüğü şeyler adlandırılmış olmalıdır; iki elemanlı bir dizi döndüren kanca, üçüncü değer eklendiğinde her çağıranı bozar. Ve ayrılan işlev tek başına sınanabilmelidir: bileşen çizmeden çağrılıp değerleri okunabiliyorsa ayrım gerçekten yapılmıştır.
Özet
- Özel kanca ve birleştirilebilir işlev, durumu ve yan etkiyi işaretleme üretmeden paketleyen birimlerdir; iki çerçeve ailesinde aynı amacın farklı sözleşmeleridir.
- Konum tabanlı çalışma zamanında kancanın kimliği çağrı sırasıdır; yuva dizisi düzdür ve iç içe kanca çağrılarının hiyerarşisini taşımaz.
- Koşullu bir kanca çağrısı yuvaları kaydırır ve hata sessizdir: istisna atılmaz, yanlış değer okunur. Kural, kancanın koşulsuz çağrılıp koşulun döndürülen değere taşınmasıdır.
- Kurulumda bir kez çalışan işlevlerde kimlik nesnedir; koşullu kurulum güvenlidir, bedeli çalışma zamanında tutulan kayıt defteridir.
- Temizlik etkin kapsama kaydedilir ve yıkım kurulumun tersi sırada yapılır; böylece bir işlevin yaslandığı şey o işlev yıkılırken hâlâ ayaktadır.
- Ayrılan işlev işaretleme döndürmez, adlandırılmış değerler döndürür ve bileşen çizmeden sınanabilir.
Sonraki Adım
Bu ders mantığı bileşenden çıkardı, ama bileşenler arasındaki ilişkiyi ele almadı. Süzgeç panelinde sekmeli bir yapı var: sekme başlıkları, sekme gövdeleri ve hangi sekmenin etkin olduğu bilgisi. Bu bilgiyi her sekmeye özellik olarak vermek, önceki derste ölçülen özellik zincirini geri getirir; her sekmenin kendi kopyasını tutması ise iki sekmenin aynı anda etkin olmasına izin verir. İhtiyaç duyulan şey, ayrı bileşenler olarak yazılmış ama tek bir durumu paylaşan bir ailedir. Sonraki ders bu ailenin nasıl kurulduğunu, aralarındaki örtük sözleşmenin nasıl denetlendiğini ve yanlış kullanımın nasıl sessiz kalmaktan çıkarıldığını gösterir.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.