İçeriğe geç
academia.sh

Ders 16 / 24

Şablonlar ve Yuvalar

Çizilmeden duran işaretleme kabı ve kopyalanmasının kuralları; yuva atama algoritması, yedek içerik, düzleştirilmiş ağaç ve yerleştirilen içeriğin hangi ağaca ait kaldığı.

İçindekiler

Önceki ders gölge ağacının nasıl yalıtıldığını gösterdi, içeriğinin nereden geldiğini göstermedi. İki açık soru kaldı. Birincisi, bileşenin iç yapısı her örnek için düğüm düğüm kurulursa on beş rozet on beş kez aynı kurulum kodunu çalıştırır. İkincisi, konağın belgede yazılı ışık ağacı hiçbir yerde görünmüyor: rozetin içine yazılan başlık ve birim kayboluyor.

Bu ders iki mekanizmayı birlikte kurar: çizilmeden duran bir işaretleme kabı ve ışık ağacındaki içeriğin gölge ağacında yerleştirileceği noktalar.

Çizilmeyen İşaretleme Kabı

HTML’de bir öge vardır ki içeriği ayrıştırılır ama belgeye girmez: şablon (template). Ayrıştırıcı şablonun içindeki işaretlemeyi düğümlere çevirir, ancak bu düğümleri belge ağacına değil şablona ait ayrı bir belge parçasına koyar.

Ayrımın sonucu içeriğin edilgen olmasıdır. Şablonun içindeki görsel indirilmez, içindeki betik çalışmaz, içindeki biçem kuralları uygulanmaz, içindeki ögeler yerleşim hesabına girmez. İşaretleme sayfada yazılıdır ama sayfanın hiçbir davranışına katılmaz.

Bu, bileşenin iç yapısını taşımak için doğru kaptır. İşaretleme belgede okunabilir biçimde durur; bileşen gerektiğinde kabın içeriğini kopyalar ve kopyayı gölge ağacına koyar. İçerik dizgiden her seferinde yeniden ayrıştırılmaz — ayrıştırma bir kez olmuştur, sonrası ağaç kopyalamadır.

Şablonun içeriğine erişmek için ögenin çocuklarına bakmak sonuç vermez; içerik ayrı bir parçadadır ve o parça üzerinden okunur. Bu, ilk karşılaşmada şaşırtıcı bulunan davranıştır: şablon ögesinin çocuk düğüm sayısı sıfırdır.

Klonlama ve Klonun Taşımadıkları

Kopyalama derin olmalıdır: yalnızca kökü değil bütün alt ağacı kopyalayan bir işlem. Sığ kopya şablonun kökünü verir, içeriğini vermez.

Kopyanın taşımadıkları, taşıdıklarından daha öğreticidir.

Dinleyiciler kopyalanmaz. Şablondaki bir düğüme dinleyici bağlanmış olsa bile kopya onu almaz; dinleyici, kopyalanan ağaç yerine yerleştirildikten sonra bağlanır. Bu bir eksiklik değil zorunluluktur: dinleyici bir işlev başvurusudur ve işaretlemenin parçası değildir.

Kimlikler kopyalanır. Şablonda bir kimlik yazılıysa her kopyada aynı kimlik oluşur. Belge ağacında bu, kimlik benzersizliğini bozar. Gölge ağacında ise sorun doğmaz: önceki derste görüldüğü gibi her gölge ağacının kendi kimlik uzayı vardır. Şablonlarda kimlik kullanımının gölge ağacına bağlı olması bundandır.

Öznitelikler kopyalanır, kullanıcı durumu kopyalanmaz. DOM API’si dersindeki ayrım burada da geçerlidir: kopya, özniteliklerin bildirdiği başlangıç durumunu alır; kaynak ögede kullanıcının değiştirdiği değer kopyaya geçmez.

Kopyalama, düğümleri belgeye bağlı olmayan bir parçada üretir. DOM API’si dersinde kurulan kural burada kendiliğinden işler: kopya hazırlanırken hiçbir yerleşim hesabı tetiklenmez, hesap yalnızca parça gölge ağacına eklendiğinde bir kez yapılır.

Yuvalar ve Atama Kuralı

Şablon bileşenin kendi yapısını verir. Bileşenin dışarıdan aldığı içerik başka bir yerden gelir: konağın ışık ağacından. Gölge ağacı, bu içeriğin yerleştirileceği noktaları yuva (slot) ögeleriyle işaretler.

Kural üç maddeye iner. Yalnızca konağın doğrudan çocukları atanabilir; torunlar atanmaz. slot özniteliği taşıyan çocuk aynı adlı yuvaya, taşımayan çocuk adsız varsayılan yuvaya gider. Hiçbir yuvanın adıyla eşleşmeyen çocuk hiçbir yere atanmaz ve çizilmez.

// yuva-atama.mjs — yuva atama algoritmasi ve duzlestirilmis agac
const oge = (ad, ozn = {}, ...cocuklar) => ({ ad, ozn, cocuklar, metin: null });
const metin = (deger) => ({ ad: "#metin", ozn: {}, cocuklar: [], metin: deger });
const yuva = (ad, ...yedek) => oge("slot", ad ? { name: ad } : {}, ...yedek);

function* agacta(kok) { yield kok; for (const c of kok.cocuklar) yield* agacta(c); }

const etiket = (d) =>
  d.metin !== null ? `"${d.metin}"`
  : d.ad === "slot" ? `slot[${d.ozn.name ?? "varsayilan"}]`
  : d.ad + (d.ozn.slot ? `[slot=${d.ozn.slot}]` : "");

// Atama: yalnizca konagin DOGRUDAN cocuklari atanir; slot ozniteligi olmayanlar
// varsayilan yuvaya, olanlar ayni adli yuvaya gider. Sira isik agacinin sirasidir.
function atamalar(konak, golgeKok) {
  const harita = new Map();
  for (const d of agacta(golgeKok)) if (d.ad === "slot") harita.set(d.ozn.name ?? "", []);
  for (const cocuk of konak.cocuklar) {
    const istenen = cocuk.ozn.slot ?? "";
    if (harita.has(istenen)) harita.get(istenen).push(cocuk);
  }
  return harita;
}

// Duzlestirilmis agac: her yuva, atanan dugumlerle; atama yoksa yedek icerigiyle degisir.
function duzlestir(dugum, harita, derinlik = 0, satirlar = []) {
  if (dugum.ad === "slot") {
    const atanan = harita.get(dugum.ozn.name ?? "") ?? [];
    const gorunen = atanan.length ? atanan : dugum.cocuklar;
    const kaynak = atanan.length ? "isik" : "yedek";
    for (const d of gorunen) {
      satirlar.push(`${"  ".repeat(derinlik)}${etiket(d)}   (${kaynak})`);
      for (const c of d.cocuklar) duzlestir(c, harita, derinlik + 1, satirlar);
    }
    return satirlar;
  }
  satirlar.push(`${"  ".repeat(derinlik)}${etiket(dugum)}`);
  for (const c of dugum.cocuklar) duzlestir(c, harita, derinlik + 1, satirlar);
  return satirlar;
}

// Konagin belgede yazili cocuklari (isik agaci)
const konak = oge("olcum-rozeti", { id: "sicaklik" },
  oge("span", { slot: "ad" }, metin("Sıcaklık")),
  oge("strong", {}, metin("-4.2")),
  oge("span", { slot: "birim" }, metin("°C")),
  oge("span", { slot: "kaynak" }, metin("istasyon-3")));

// Golge agaci
const golgeKok = oge("#golge-kok", {},
  oge("div", { class: "kutu" },
    yuva("ad", metin("Ölçüm")),
    oge("output", {}, yuva(null, metin("—"))),
    yuva("birim", metin("birimsiz"))));

const harita = atamalar(konak, golgeKok);
console.log("atamalar:");
for (const [ad, dugumler] of harita)
  console.log(`  slot[${ad || "varsayilan"}] ←`, dugumler.map(etiket).join(", ") || "(bos → yedek icerik)");

const atanmamis = konak.cocuklar.filter((c) => !harita.has(c.ozn.slot ?? ""));
console.log("atanmayan isik dugumleri:", atanmamis.map(etiket).join(", ") || "(yok)");

console.log("\nduzlestirilmis agac:");
console.log(duzlestir(golgeKok, harita).join("\n"));

// Isik agaci bos bir konak ayni golge agaciyla: her yuva yedek icerigini gosterir.
const bosKonak = oge("olcum-rozeti", { id: "nem" });
console.log("\nisik agaci bos konak:");
console.log(duzlestir(golgeKok, atamalar(bosKonak, golgeKok)).join("\n"));

// Isik agaci degisirse atama yeniden hesaplanir.
konak.cocuklar.push(oge("em", { slot: "birim" }, metin("°F")));
const yeni = atamalar(konak, golgeKok);
console.log("\nisik agacina em[slot=birim] eklendikten sonra:");
for (const [ad, dugumler] of yeni) {
  const oncekiler = harita.get(ad).map(etiket).join(",");
  const simdikiler = dugumler.map(etiket).join(",");
  console.log(`  slot[${ad || "varsayilan"}] ${oncekiler === simdikiler ? "degismedi" : "degisti → " + (simdikiler || "(bos)")}`);
}
atamalar:
  slot[ad] ← span[slot=ad]
  slot[varsayilan] ← strong
  slot[birim] ← span[slot=birim]
atanmayan isik dugumleri: span[slot=kaynak]

duzlestirilmis agac:
#golge-kok
  div
    span[slot=ad]   (isik)
      "Sıcaklık"
    output
      strong   (isik)
        "-4.2"
    span[slot=birim]   (isik)
      "°C"

isik agaci bos konak:
#golge-kok
  div
    "Ölçüm"   (yedek)
    output
      "—"   (yedek)
    "birimsiz"   (yedek)

isik agacina em[slot=birim] eklendikten sonra:
  slot[ad] degismedi
  slot[varsayilan] degismedi
  slot[birim] degisti → span[slot=birim],em[slot=birim]

Çıktının ilk bölümünde dikkat çeken satır sonuncusudur: slot="kaynak" yazan çocuk hiçbir yuvayla eşleşmediği için atanmamış, dolayısıyla çizilmemiştir. Ağaçtan silinmemiştir, belgede durmaktadır; yalnızca düzleştirilmiş ağaca girmemiştir. Bu, bileşen kullanıcısının yaptığı yazım hatalarının sessiz kaybolmasıyla sonuçlanır ve bileşenin belgelenmiş yuva adlarına bağımlılığını gösterir.

Düzleştirilmiş Ağaç

Tarayıcının gerçekten çizdiği şey ne belge ağacı ne gölge ağacıdır; her yuvanın kendisine atanan düğümlerle değiştirilmiş hâlidir. Buna düzleştirilmiş ağaç (flattened tree) denir ve yerleşim ile boyama bu ağaç üzerinden yapılır.

Çıktının ikinci bölümü bunu gösterir. slot ögeleri düzleştirilmiş ağaçta görünmez; yerlerinde ışık ağacından gelen düğümler durur. strong ögesi belge ağacında konağın doğrudan çocuğudur, ama çizim sırasında output ögesinin içindedir. Düğümün ağaçtaki yeri ile çizimdeki yeri ayrışmıştır.

Üçüncü bölüm yedek içeriğin (fallback content) rolünü gösterir. Bir yuvanın kendi çocukları, o yuvaya hiçbir şey atanmadığında çizilir. Bu, bileşene varsayılan bir görünüm kazandırır: ışık ağacı boş bir rozet birimsiz ve değersiz değil, yer tutucularıyla görünür. Yedek içerik atama olduğu anda tümüyle devre dışı kalır; atananla karışmaz.

Son bölüm ışık ağacındaki değişikliğin atamayı yeniden hesaplattığını gösterir. Yuva ögeleri, atamaları değiştiğinde bir olay yayar; içeriğine bağlı bir hesap yapması gereken bileşen bu olayı dinler. Olayın çizim döngüsü içindeki yerini Zamanlama ve Çizim Döngüsü dersindeki model verir: atama değişimi, değişikliği yapan koddan sonra bildirilir.

Yerleştirilen İçeriğin Sahibi

Yerleştirme, düğümün ağacını değiştirmez. Yuvaya atanan strong ögesi belge ağacında kalır; yalnızca çizilirken gölge ağacındaki bir noktaya taşınır. Sahiplik değişmediği için üç davranış buradan türer.

Biçem belgeden gelir. Yerleştirilen düğüm belge ağacının kuralı ile biçemlenir; gölge ağacındaki kurallar ona genel olarak eşleşmez. Bileşen, yerleştirilen düğümlere sınırlı bir biçem verebilir: yuvaya atanmış düğümleri hedefleyen özel bir seçici vardır ve yalnızca atanmış düğümün kendisine — çocuklarına değil — eşleşir. Bu kısıt bilinçlidir: bileşen, kullanıcısının verdiği içeriğin iç yapısını biçemleyemez, yalnızca dış kutusunu hizalayabilir. Çakışmada belgenin kuralı üstün gelir; bileşenin yerleştirilen içeriğe verdiği biçem bir öneridir.

Olaylar sınır aşımı yaşamaz. Yerleştirilen düğümün ataları belge ağacındadır; oradaki bir olayın hedefi çevrilmez, çünkü olay hiçbir gölge sınırı geçmemiştir. Bileşen, ışık ağacındaki içeriğe bağlanmış dinleyicileri göremez ve görmemelidir.

Erişilebilirlik ağacı düzleştirilmiş ağaçtan türer. Yardımcı teknolojiye sunulan yapı, kullanıcının gördüğü sıradır. Yuvaların gölge ağacındaki sırası okuma sırasını belirler; ışık ağacındaki yazım sırası değil. Bileşen tasarlarken yuva sırası bir görsel tercih değil, anlam taşıyan bir karardır.

Özet

  • Şablon ögesinin içeriği ayrıştırılır ama belgeye girmez; edilgendir, hiçbir kaynak yüklemez ve çizime katılmaz.
  • Şablon içeriği derin kopyayla çoğaltılır; kopya öznitelikleri ve kimlikleri taşır, dinleyicileri ve kullanıcı durumunu taşımaz.
  • Yuva atama kuralı yalnızca konağın doğrudan çocuklarına uygulanır; eşleşmeyen ad hiçbir yere atanmaz ve çizilmez.
  • Çizilen yapı düzleştirilmiş ağaçtır: gölge ağacındaki her yuva, atanan düğümlerle ya da atama yoksa yedek içeriğiyle değiştirilmiş hâlidir.
  • Yerleştirilen düğüm ışık ağacına ait kalır: belgenin biçemiyle biçemlenir, olayları gölge sınırı geçmez; bileşenin ona verebileceği biçem sınırlıdır.

Sonraki Adım

Buraya kadar kurulan her şey sayfa yüklendikten sonra geçerlidir: öge tanımlanır, gölge ağacı takılır, yuvalar dolar. Hepsinin öncesinde tek bir varsayım durur — bileşenin kodu, şablonu ve verisi ağdan gelmiştir. Ağ bağlantısı zayıfladığında ya da tümüyle kesildiğinde bu varsayım düşer ve sayfa, ne kadar iyi kapsüllenmiş olursa olsun, boş bir hata ekranına iner. İstasyon sayfasının kullanıldığı yer buna elverişli değildir: ölçüm alan kişi çoğunlukla kapsama alanının kıyısındadır. Sonraki ders, sayfanın isteklerini ağa ulaşmadan önce karşılayabilen ve sayfadan bağımsız yaşayan bir katmanı kurar.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat