Ders 17 / 25
Bileşik Bileşenler
Tek bir durumu paylaşan bileşen ailesi; örtük paylaşımın özellik yüzeyine etkisi, ilişkisel özniteliklerin bileşen tarafından kurulması ve örtük sözleşmenin çalışma zamanında denetlenmesi.
İçindekiler
Süzgeç paneli büyüdükçe iki bölüme ayrıldı: eşik ayarları ve tarih aralığı. İkisi aynı anda görünmüyor; kullanıcı bir sekme başlığına basarak birinden diğerine geçiyor. Yapı üç parçadan oluşuyor — başlıkların listesi, başlıkların kendisi ve gövdeler — ve üçü de “hangisi etkin” bilgisini bilmek zorunda.
Bu bilgiyi her parçaya özellik olarak vermek, ilk derste ölçülen özellik zincirini geri getirir. Her parçanın kendi kopyasını tutması ise iki gövdenin aynı anda görünmesine izin verir. Gereken şey, ayrı bileşenler olarak yazılmış ama tek bir durumu paylaşan bir ailedir.
Aile ve Örtük Paylaşım
Bileşik bileşen (compound component), tek başına anlamlı olmayan üyelerden kurulu bir bileşen kümesidir. Üst üye durumu tutar ve sağlar; alt üyeler o durumu doğrudan okur. Çağıran, durumu üyelerin arasında elden ele taşımaz.
Paylaşım, Bağlam Aktarımı dersindeki mekanizmayla yapılır: üst üye bir değeri ağacın o noktasında sağlar, alt üyeler kendi çizimleri sırasında okur. Bunun çalışması için tek bir koşul vardır — alt üyeler üst üyenin çiziminden sonra çizilmelidir. Bileşen ağacının tembel kurulması bu koşulu kendiliğinden sağlar.
Üyelerin üst bileşenin bir özelliği olarak adlandırılması (Sekmeler.Başlık gibi) teknik
bir zorunluluk değildir; aidiyeti kaynak metinde görünür kılan bir adlandırma kararıdır.
// bilesik.mjs — birlikte calisan bilesen ailesi ve ortulu sozlesmesi const oge = (ad, ozn = {}, ...cocuklar) => ({ ad, ozn, cocuklar: cocuklar.flat().filter(Boolean) }); const metin = (deger) => ({ ad: "#metin", ozn: {}, cocuklar: [], metin: deger }); // Baglam yigiti: cocuklar ust bilesenin cizimi sirasinda calistigi icin okuyabilirler. const yigit = []; function baglamSagla(deger, cocuklariCiz) { yigit.push(deger); try { return cocuklariCiz(); } finally { yigit.pop(); } } function baglamOku(cagiran) { const d = yigit[yigit.length - 1]; if (!d) throw new Error(`${cagiran} yalnızca Sekmeler bileşeninin içinde kullanılabilir`); return d; } // Ozellik sayaci: cagiran tarafin yazdigi ozellikleri say. let yazilanOzellik = 0; const say = (o) => { yazilanOzellik += Object.keys(o).length; return o; }; // --- Bilesik bilesen ailesi --- const Sekmeler = (o) => { say(o); const paylasilan = { etkin: o.etkin, sec: o.sec, kok: o.kimlik }; return oge("div", { class: "sekmeler" }, baglamSagla(paylasilan, o.cocuklar)); }; Sekmeler.Liste = (o) => { say(o); baglamOku("Sekmeler.Liste"); return oge("div", { role: "tablist" }, o.cocuklar()); }; Sekmeler.Baslik = (o) => { say(o); const b = baglamOku("Sekmeler.Başlık"); const secili = b.etkin === o.ad; return oge("button", { id: `${b.kok}-baslik-${o.ad}`, role: "tab", type: "button", "aria-selected": String(secili), "aria-controls": `${b.kok}-govde-${o.ad}`, tabindex: secili ? 0 : -1, }, metin(o.metin)); }; Sekmeler.Govde = (o) => { say(o); const b = baglamOku("Sekmeler.Gövde"); if (b.etkin !== o.ad) return null; return oge("div", { id: `${b.kok}-govde-${o.ad}`, role: "tabpanel", "aria-labelledby": `${b.kok}-baslik-${o.ad}`, tabindex: 0, }, o.cocuklar()); }; // --- Kullanim --- const yaz = (d, g = 0) => { if (!d) return []; if (d.metin !== undefined) return [`${" ".repeat(g)}"${d.metin}"`]; const oznMetni = Object.entries(d.ozn).map(([a, v]) => ` ${a}="${v}"`).join(""); return [`${" ".repeat(g)}<${d.ad}${oznMetni}>`, ...d.cocuklar.flatMap((c) => yaz(c, g + 1))]; }; yazilanOzellik = 0; const agac = Sekmeler({ kimlik: "suzgec", etkin: "esik", sec: () => {}, cocuklar: () => [ Sekmeler.Liste({ cocuklar: () => [ Sekmeler.Baslik({ ad: "esik", metin: "Eşik" }), Sekmeler.Baslik({ ad: "aralik", metin: "Tarih aralığı" }), ] }), Sekmeler.Govde({ ad: "esik", cocuklar: () => [metin("-40 ile 40 arası")] }), Sekmeler.Govde({ ad: "aralik", cocuklar: () => [metin("son 24 saat")] }), ], }); const bilesikOzellik = yazilanOzellik; console.log(yaz(agac).join("\n")); // Ayni agac, paylasilan durum her cocuga elle verilerek. yazilanOzellik = 0; const AcikBaslik = (o) => { say(o); return oge("button", { id: `${o.kok}-baslik-${o.ad}`, role: "tab", type: "button", "aria-selected": String(o.etkin === o.ad), "aria-controls": `${o.kok}-govde-${o.ad}`, tabindex: o.etkin === o.ad ? 0 : -1 }, metin(o.metin)); }; const AcikGovde = (o) => { say(o); return o.etkin !== o.ad ? null : oge("div", { id: `${o.kok}-govde-${o.ad}`, role: "tabpanel", "aria-labelledby": `${o.kok}-baslik-${o.ad}`, tabindex: 0 }, o.cocuklar()); }; const acikAgac = oge("div", { class: "sekmeler" }, oge("div", { role: "tablist" }, AcikBaslik({ kok: "suzgec", etkin: "esik", sec: () => {}, ad: "esik", metin: "Eşik" }), AcikBaslik({ kok: "suzgec", etkin: "esik", sec: () => {}, ad: "aralik", metin: "Tarih aralığı" })), AcikGovde({ kok: "suzgec", etkin: "esik", ad: "esik", cocuklar: () => [metin("-40 ile 40 arası")] }), AcikGovde({ kok: "suzgec", etkin: "esik", ad: "aralik", cocuklar: () => [metin("son 24 saat")] })); console.log("\niki ağaç aynı mı:", JSON.stringify(agac) === JSON.stringify(acikAgac)); console.log(`çağıranın yazdığı özellik — bileşik: ${bilesikOzellik}, açık aktarım: ${yazilanOzellik}`); // --- Ortulu sozlesmenin denetimi --- console.log("\naile dışında kullanım:"); try { Sekmeler.Baslik({ ad: "esik", metin: "Eşik" }); } catch (h) { console.log(" ", h.message); } console.log("\nyanlış ada bağlanan gövde:"); yigit.length = 0; const kirik = Sekmeler({ kimlik: "suzgec", etkin: "esik", sec: () => {}, cocuklar: () => [ Sekmeler.Liste({ cocuklar: () => [Sekmeler.Baslik({ ad: "esik", metin: "Eşik" })] }), Sekmeler.Govde({ ad: "esikk", cocuklar: () => [metin("-40 ile 40 arası")] }), ], }); const topla = (d, f, c = []) => { if (!d) return c; if (f(d)) c.push(d); d.cocuklar?.forEach((x) => topla(x, f, c)); return c; }; const basliklar = topla(kirik, (d) => d.ozn.role === "tab"); const govdeler = topla(kirik, (d) => d.ozn.role === "tabpanel"); console.log(" başlık sayısı:", basliklar.length, "| gövde sayısı:", govdeler.length); for (const b of basliklar) { const hedef = govdeler.find((g) => g.ozn.id === b.ozn["aria-controls"]); console.log(` ${b.ozn.id} → aria-controls="${b.ozn["aria-controls"]}" ${hedef ? "hedef var" : "HEDEF YOK"}`); }
<div class="sekmeler">
<div role="tablist">
<button id="suzgec-baslik-esik" role="tab" type="button" aria-selected="true" aria-controls="suzgec-govde-esik" tabindex="0">
"Eşik"
<button id="suzgec-baslik-aralik" role="tab" type="button" aria-selected="false" aria-controls="suzgec-govde-aralik" tabindex="-1">
"Tarih aralığı"
<div id="suzgec-govde-esik" role="tabpanel" aria-labelledby="suzgec-baslik-esik" tabindex="0">
"-40 ile 40 arası"
iki ağaç aynı mı: true
çağıranın yazdığı özellik — bileşik: 13, açık aktarım: 18
aile dışında kullanım:
Sekmeler.Başlık yalnızca Sekmeler bileşeninin içinde kullanılabilir
yanlış ada bağlanan gövde:
başlık sayısı: 1 | gövde sayısı: 0
suzgec-baslik-esik → aria-controls="suzgec-govde-esik" HEDEF YOK
Ailenin Ürettiği İlişkiler
Çıktının ilk bölümünde çağıranın hiç yazmadığı beş öznitelik var: iki kimlik, aria-selected,
aria-controls ve aria-labelledby. Bunlar üyeler arasındaki ilişkiyi kuran
özniteliklerdir; başlık hangi gövdeyi denetlediğini, gövde hangi başlıkla adlandırıldığını
bunlarla bildirir.
İlişkiyi ailenin kurması iki nedenle önemlidir. Birincisi, kimliklerin benzersizliği tek bir kökten türetilir; aynı sayfada ikinci bir sekme grubu farklı bir kök adı alır ve kimlikler çakışmaz. İkincisi, ilişki çağırana bırakılmaz. Web’in Temelleri ve HTML kursundaki Etiket–Alan İlişkisi dersinde görülen sorun burada da geçerlidir: elle yazılan bir ilişki bir yerde unutulur, unutulduğunda görsel olarak hiçbir şey değişmez ve yalnızca yardımcı teknoloji kullanıcısı etkilenir.
Odak yönetimi de aileye aittir. Çıktıda etkin olmayan başlığın tabindex değeri -1;
Tarayıcı ve Web Platformu kursundaki dönen sekme durağı düzeni budur. Sekme listesi klavye
sırasında tek durak tutar, listenin içinde ok tuşlarıyla gezilir.
Örtük Paylaşımın Bedeli
İki ağaç birebir aynı. Fark yalnızca çağıranın yazdığı özellik sayısında: bileşik ailede 13, açık aktarımda 18. Aradaki beş, paylaşılan durumun her üyeye elle verilmesinden gelir ve üye sayısıyla birlikte artar. Dört sekmeli bir grupta fark on olur.
Kazancın karşılığı keşfedilebilirliğin azalmasıdır. Sekmeler.Başlık bileşeninin
imzasına bakıldığında hangi durumu okuduğu görünmez; imzada yalnızca ad ve metin vardır.
Bileşenin gerçek girdisi imzasında yazmayan bir bağlamdır. Bu, ilk derste sarmalayan
bileşenin gövdesinde biriken sessiz kararların bir benzeridir; burada karar gövdede değil,
ağaçtaki konumdadır.
İkinci bedel, üyenin tek başına taşınamamasıdır. Sekmeler.Başlık ailesinden koparıldığında
çalışmaz. Bu bir kusur değil tanımdır — ancak tanımın çalışma zamanında görünür olması
gerekir.
Örtük Sözleşmenin Denetimi
Çıktının üçüncü bölümü, aile dışında çağrılan üyenin ne yaptığını gösteriyor: adıyla birlikte nereye ait olduğunu söyleyen bir istisna. Bağlamı okumadan önce varlığını sınamak tek satırlık bir denetimdir ve bileşik bileşenlerde zorunludur. Denetim olmasaydı, tanımsız bir değerden alan okumaya çalışan anlamsız bir hata alınırdı.
Dördüncü bölüm daha sinsi bir kusuru gösteriyor. Gövdenin adı esikk yazılmış; başlık
esik adına bağlı. Hiçbir istisna atılmıyor, çünkü her iki üye de aile içinde ve bağlamı
okuyabiliyor. Sonuç ağaçta bir sekme başlığı ve hiç gövde olması, başlığın aria-controls
özniteliğinin var olmayan bir kimliği göstermesi. Bu, Şablonlar ve Yuvalar dersindeki
eşleşmeyen yuva adıyla aynı sınıftan bir hatadır: yazım hatası sessizce kaybolur.
Bu tür kusurların denetimi iki yerde yapılabilir. Çalışma zamanında, üst üye kendi altındaki üyelerin adlarını toplayıp eşleşmeyenleri bildirir; bedeli, ağaç üzerinde ek bir geçiştir. Tip düzeyinde, üye adları sabit bir birlik tipinden alınır ve eşleşmeyen ad derlenmez; bedeli, adların çalışma zamanında üretilememesidir. İkinci yol daha ucuzdur ve yakalayabildiği hatayı erken yakalar.
Ne Zaman Aile Kurulmaz
Bileşik bileşen, üyeler arasında gerçek bir paylaşılan durum olduğunda doğrudur. Sekme grubunda böyle bir durum vardır: hangisinin etkin olduğu. Ölçüm tablosunun başlık ve gövde bölümlerinde yoktur; ikisi de yalnızca yan yana durur. Böyle bir yerde aile kurmak, örtük paylaşımın bedelini karşılığı olmadan ödemektir; yuva tabanlı bileşim yeter.
İkinci sınır üye sayısıdır. Yedi üyeli bir ailede çağıranın öğrenmesi gereken şey bir bileşen değil, küçük bir dildir; üyeler arasındaki geçerli iç içe geçme kuralları da bu dilin sözdizimi olur. Böyle bir yüzey ancak yeniden kullanım sayısı yüksekse karşılığını verir.
Üçüncü sınır, ailenin sıraya bağımlı olmasıdır. Üst üye alt üyelerin belirli bir sırada geldiğini varsayıyorsa, çağıranın araya koyacağı bir sarmalayıcı düzeni bozar. Bağlam üzerinden kurulan paylaşım bu bağımlılığı taşımaz — bağlam ağacın derinliği boyunca okunur, sıra gözetmez — ve bu, doğrudan çocuk listesini tarayan tasarımlara üstünlüğüdür.
Özet
- Bileşik bileşen, tek başına anlamlı olmayan üyelerden kurulu ve tek bir durumu bağlam üzerinden paylaşan bir ailedir.
- Paylaşımın koşulu, alt üyelerin üst üyenin çiziminden sonra çizilmesidir; bileşen ağacının tembel kurulması bunu sağlar.
- Aile, üyeler arasındaki kimlik ve ilişki özniteliklerini kendisi üretir; bu, elle yazılan ilişkilerde görülen sessiz kusurları ortadan kaldırır.
- Örtük paylaşım çağıranın yazdığı özellik sayısını düşürür, karşılığında üyenin girdisini imzasından okunamaz hâle getirir.
- Aile dışında kullanım çalışma zamanında istisna ile bildirilmelidir; üyeler arası ad eşleşmesi ise tip düzeyinde ya da ağaç taramasıyla denetlenir.
- Paylaşılan durum yoksa aile kurulmaz; yuva tabanlı bileşim aynı yapıyı bedelsiz verir.
Sonraki Adım
Sekme ailesi bir şeyi hâlâ birbirine karıştırıyor: hangi sekmenin etkin olduğunu tutmakla sekmenin nasıl göründüğüne karar vermek aynı bileşende. Bu, küçük bir ailede sorun çıkarmaz, ama ölçüm tablosunda çıkarır — tablo veriyi getiriyor, sıralıyor, sayfalıyor ve aynı zamanda hücreleri çiziyor. Böyle bir bileşen ne veri katmanı değişmeden sınanabilir ne de başka bir veri kaynağıyla yeniden kullanılabilir. Sonraki ders bu iki sorumluluğu ayırır ve ayrımın sınanabilirlik üzerindeki etkisini sahte bağımlılık sayısıyla ölçer.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.