---
title: 'Bileşen Tabanlı Arayüz Geliştirme'
source: 'https://academia.sh/tr/kurslar/bilesen-tabanli-gelistirme'
language: tr
updated: '2026-08-17T04:40:49+00:00'
license: 'CC BY-SA 4.0'
lessons: 25
outcomes:
    - 'Bildirimsel arayüz modelini ve yeniden çizim mantığını açıklar'
    - 'Bileşen bileşimiyle yeniden kullanılabilir arayüz kurar'
    - 'Çerçeve aileleri arasındaki tasarım farklarını karşılaştırır'
---

# Bileşen Tabanlı Arayüz Geliştirme

Arayüzü adım adım güncellemek yerine durumdan türeten bildirimsel model, bileşenlerin girdi–çıktı sözleşmesi ve bileşimi, yeniden çizimin uzlaşmayla ağaca yansıtılması ve çerçeve ailelerinin tasarım farkları.

## Bildirimsel Arayüz Modeli

Elle güncellemenin ölçek sorunları, durumdan görünüme tek yönlü akış, bileşenin girdi–çıktı sözleşmesi, işaretlemenin veriye bağlanması, koşullu ve listeli oluşturmada kimlik korunumu ve uzlaşma algoritması.

### Neden Çerçeve

Source: https://academia.sh/tr/kurslar/bilesen-tabanli-gelistirme/neden-cerceve

Arayüzü elle güncellemenin ölçek sorunları; güncelleme yollarının sayısı, durumun iki kopyada tutulması, kaymanın kendini onaran biçimi ve bir arayüz çerçevesinin devraldığı iş.

Tarayıcı ve Web Platformu kursu istasyon sayfasını programın erişebildiği bir veri
yapısına dönüştürdü: düğüm oluşturuldu, dinleyici bağlandı, değişiklik ilgili ögeye
yazıldı, sökülürken hepsi geri alındı. Ölçüm rozeti bir bileşene dönüştü, ama panodaki on
beş rozetin ölçüm listesiyle eşzamanlı kalması hâlâ elle yazılmış güncelleme koduna bağlı.
Bir kayıt eklendiğinde hangi düğümlerin değişeceğini kodun kendisi hesaplıyor.

Bu ders o hesabın neden büyüdükçe hata kaynağına dönüştüğünü ölçer. Sorunun adı beceriksiz
kod değildir; **buyurgan (imperative)** güncellemenin yapısal bir özelliğidir ve pano
büyüdükçe kaçınılmaz hâle gelir.

#### Elle Güncellemenin Üç İşi

Arayüzün her parçası için üç ayrı kod yolu yazılır.

**İlk oluşturma** düğümleri kurar, özniteliklerini yazar, dinleyicileri bağlar.
**Güncelleme**, durum değiştiğinde var olan düğümlerin hangi alanlarının yeni değeri
alacağını belirler. **Sökme**, parça kaldırılırken dinleyicileri kaldırır, zamanlayıcıları
durdurur, gözlemcileri çözer.

Üçü de aynı görünümü betimler ama üçü ayrı yerde yazılıdır. Doğruluk koşulu şudur: ilk
oluşturmanın ürettiği ağaç ile, aynı duruma güncelleme yoluyla ulaşılmış ağaç birbirinin
aynısı olmalıdır. Bu koşulu hiçbir şey denetlemez. İlk oluşturmaya bir sınıf eklenip
güncellemeye eklenmediğinde sayfa ilk yüklemede doğru, ilk etkileşimden sonra yanlış olur.

#### Güncelleme Yollarının Sayısı

Güncelleme kodunun büyüklüğü parça sayısıyla değil, **durum alanı ile parça arasındaki
bağlantı sayısıyla** ölçülür. Bir alan kaç parçayı besliyorsa, o alanı değiştiren her
işleyicinin o kadar yeri güncellemesi gerekir.

```js
// elle-guncelleme.mjs — durum alani ile arayuz parcasi arasindaki guncelleme yollari
// Panodaki her arayuz parcasinin hangi durum alanlarini okudugunu bildiriyoruz.
const PANO = {
  "rozet.deger": ["olcumler"],
  "rozet.renk": ["olcumler", "esikler"],
  "tablo.satirlar": ["olcumler", "suzgec"],
  "tablo.sayac": ["olcumler", "suzgec"],
  "suzgec.secim": ["suzgec"],
  "baslik.zaman": ["sonGuncelleme"],
};

// Bir durum alani degistiginde elle yazilmasi gereken guncelleme yollari:
// o alani okuyan her parca icin bir yol.
function ozet(baslik, parcalar) {
  const tablo = new Map();
  for (const [parca, alanlar] of Object.entries(parcalar))
    for (const alan of alanlar) tablo.set(alan, [...(tablo.get(alan) ?? []), parca]);

  let toplam = 0;
  console.log(baslik);
  for (const [alan, liste] of [...tablo].sort()) {
    toplam += liste.length;
    console.log(`  ${alan.padEnd(14)} ${String(liste.length).padStart(2)} yol  ${liste.join(", ")}`);
  }
  console.log(`  ${String(Object.keys(parcalar).length).padStart(2)} parca, ` +
    `${String(tablo.size).padStart(2)} alan, ${String(toplam).padStart(2)} yol\n`);
  return { parca: Object.keys(parcalar).length, alan: tablo.size, yol: toplam };
}

const once = ozet("Pano — ilk hali", PANO);

// Iki istek: (1) olcum birimi degistirilebilsin — birim, satirlari da rozeti de
// etkiler; (2) pano bir ozet grafigi ve bir esik uyarisi da gostersin.
const SONRA = ozet("Pano — iki istek sonrasi", {
  ...PANO,
  "rozet.birim": ["birim"],
  "tablo.satirlar": ["olcumler", "suzgec", "birim"],
  "grafik.noktalar": ["olcumler", "suzgec", "birim"],
  "uyari.serit": ["olcumler", "esikler", "suzgec"],
});

const yuzde = (y, e) => `%${Math.round((y / e - 1) * 100)}`;
console.log(`parca ${yuzde(SONRA.parca, once.parca)}, ` +
  `alan ${yuzde(SONRA.alan, once.alan)}, yol ${yuzde(SONRA.yol, once.yol)} artti`);
```

```
Pano — ilk hali
  esikler         1 yol  rozet.renk
  olcumler        4 yol  rozet.deger, rozet.renk, tablo.satirlar, tablo.sayac
  sonGuncelleme   1 yol  baslik.zaman
  suzgec          3 yol  tablo.satirlar, tablo.sayac, suzgec.secim
   6 parca,  4 alan,  9 yol

Pano — iki istek sonrasi
  birim           3 yol  tablo.satirlar, rozet.birim, grafik.noktalar
  esikler         2 yol  rozet.renk, uyari.serit
  olcumler        6 yol  rozet.deger, rozet.renk, tablo.satirlar, tablo.sayac, grafik.noktalar, uyari.serit
  sonGuncelleme   1 yol  baslik.zaman
  suzgec          5 yol  tablo.satirlar, tablo.sayac, suzgec.secim, grafik.noktalar, uyari.serit
   9 parca,  5 alan, 17 yol

parca %50, alan %25, yol %89 artti
```

Parça sayısı yarı yarıya arttı, elle yazılacak güncelleme yolu neredeyse ikiye katlandı.
Bunun nedeni, yeni parçaların var olan alanları da okumasıdır: grafik `olcumler`, `suzgec`
ve `birim` alanlarına bağlanınca bu üç alanı değiştiren her işleyiciye bir satır daha
eklenmesi gerekti.

Bağlantı sayısı en kötü durumda alan sayısı ile parça sayısının çarpımına yaklaşır. Bu
çarpım, tek bir kişinin akılda tutabileceği bir sayı olmaktan hızla çıkar; sayfaya yeni
bir parça ekleyen kişinin, o parçanın okuduğu her alanı kimin nerede değiştirdiğini
bilmesi gerekir.

#### Durumun İki Kopyası

Elle güncellemede durum iki yerde bulunur: program değişkenlerinde ve ağacın kendisinde.
Ağaçtaki kopya eksiktir ve kayıplıdır.

Ağaç yalnızca dizgi saklar. Bir sayı öznitelik olarak yazıldığında tipini kaybeder; `-4.2`
ile `"-4.2"` arasındaki farkı geri getirmek okuyan tarafın işidir. Ağaç, ekranda
görünmeyen durumu hiç saklamaz: süzgeç panelindeki seçim bir sınıf adına indirgenmişse,
seçimin gerekçesi ya da önceki değeri kaybolmuştur.

Buradan bir kural çıkar: **ağaç bir depo değil, bir çıktıdır.** Ağaçtan okuyarak karar
veren kod, kendi ürettiği çıktıyı girdi olarak kullanır ve iki kopya arasındaki her
ayrışmada yanlış karar verir.

#### Kaymanın Anatomisi

İki kopyanın ayrışmasına **kayma** denir. Aşağıdaki model kaymayı, durumdan hesaplanan
beklenen metinle ağaçtaki metni karşılaştıran bir denetimle yakalar.

```js
// kayma.mjs — elle guncellemede durum ile agacin ayrisma anini yakalar
// Kucuk bir agac modeli: her dugumun bir adi ve bir metni var.
const agac = {
  "tablo.sayac": { metin: "" },
  "tablo.satirlar": { metin: "" },
  "suzgec.secim": { metin: "" },
};
const yaz = (ad, metin) => { agac[ad].metin = metin; };

const durum = {
  olcumler: [
    { ad: "Sıcaklık", tur: "sicaklik", deger: -4.2 },
    { ad: "Bağıl nem", tur: "nem", deger: 72 },
    { ad: "Rüzgâr", tur: "ruzgar", deger: 11.4 },
    { ad: "Kar derinliği", tur: "kar", deger: 38 },
  ],
  suzgec: "tumu",
  esik: 50,
};

// Durumdan beklenen metinler. Denetim bunlari agactakiyle karsilastirir.
const gorunenler = (d) =>
  d.suzgec === "tumu" ? d.olcumler : d.olcumler.filter((o) => o.deger >= d.esik);
const beklenen = (d) => ({
  "tablo.sayac": `${gorunenler(d).length} / ${d.olcumler.length} ölçüm`,
  "tablo.satirlar": gorunenler(d).map((o) => o.ad).join(" | "),
  "suzgec.secim": d.suzgec,
});

// Ilk kurulum: her dugum elle yaziliyor.
for (const [ad, metin] of Object.entries(beklenen(durum))) yaz(ad, metin);

// Olay isleyicileri agaca dogrudan yaziyor. Ikisi de eksiksiz gorunuyor.
function suzgecDegisti(yeni) {
  durum.suzgec = yeni;
  yaz("suzgec.secim", yeni);
  yaz("tablo.satirlar", gorunenler(durum).map((o) => o.ad).join(" | "));
  yaz("tablo.sayac", `${gorunenler(durum).length} / ${durum.olcumler.length} ölçüm`);
}
function olcumEklendi(olcum) {
  durum.olcumler.push(olcum);
  yaz("tablo.satirlar", gorunenler(durum).map((o) => o.ad).join(" | "));
  // "tablo.sayac" burada yazilmiyor: ekleme yolunu yazan kisi sayaci gormedi.
}

function denetle(etiket) {
  const b = beklenen(durum);
  const kayanlar = Object.keys(b).filter((ad) => agac[ad].metin !== b[ad]);
  console.log(`${etiket}: ${kayanlar.length ? "kayma var" : "tutarli"}`);
  for (const ad of kayanlar)
    console.log(`  ${ad}\n    agac    : ${agac[ad].metin}\n    beklenen: ${b[ad]}`);
}

denetle("kurulum");
suzgecDegisti("esikUstu");
denetle("suzgec degisti");
olcumEklendi({ ad: "Basınç", tur: "basinc", deger: 964 });
denetle("olcum eklendi");
suzgecDegisti("esikUstu");
denetle("suzgec yeniden secildi");
```

```
kurulum: tutarli
suzgec degisti: tutarli
olcum eklendi: kayma var
  tablo.sayac
    agac    : 1 / 4 ölçüm
    beklenen: 2 / 5 ölçüm
suzgec yeniden secildi: tutarli
```

Çıktının üç yeri okunmalıdır.

Birincisi, hata **ekleme yolunun eksikliğinden** doğar, ekleme kodunun yanlışlığından
değil. `olcumEklendi` yazdığı satırı doğru yazar; yazmadığı satır sorundur. Bu tür bir
eksiklik derleyici, tip denetimi ya da birim testi tarafından yakalanmaz, çünkü ortada
çağrılmayan bir işlev yoktur.

İkincisi, kayma **kendini onarır**. Süzgeç yeniden seçildiğinde sayaç doğru değeri alır ve
tutarlılık geri gelir. Kendini onaran hata, yeniden üretilmesi en zor hata türüdür:
kullanıcının anlattığı sırayı birebir izlemeyen hiç kimse onu göremez.

Üçüncüsü, aynı metin **üç ayrı yerde** hesaplanmıştır — ilk kurulumda, süzgeç işleyicisinde
ve denetimde. Bu tekrar, kaymanın kaynağıdır. Metin tek bir yerde hesaplansaydı eksik
güncelleme olanaksız olurdu.

#### Sıra, Kimlik ve Temizlik

Liste güncellemesi elle yazıldığında üç sorun daha eklenir.

**Sıra.** Listenin ortasına bir kayıt girdiğinde hangi düğümün nereye taşınacağını çağıran
kod hesaplar. Kolay yol, listeyi silip yeniden kurmaktır; bu, odaklanmış girdiyi kaybeder,
kaydırma konumunu sıfırlar ve süren geçişleri keser.

**Kimlik.** Bir düğüm hangi kayda karşılık geliyor sorusunun yanıtı ağaçta yazılı değildir.
Dizinle eşleştirilen düğümler, listenin başına ekleme yapıldığında bir kayarak yanlış
kayıtla eşleşir. Bu bağ elle kurulduğunda genellikle bir veri özniteliğine yazılır ve her
sıralamada güncellenmesi gerekir.

**Temizlik.** Kaldırılan her düğümün dinleyicisi kaldırılmalıdır; kaldırılmazsa Bellek
İncelemesi dersindeki kopmuş düğümler birikir. Sökme kodunun oluşturma kodunun tersini tam
olarak yapması gerekir ve bu simetriyi de hiçbir şey denetlemez.

#### Çerçevenin Devraldığı İş

Buradaki sorunların ortak kaynağı tektir: **arayüz, nasıl değiştirileceğiyle
tanımlanmıştır.** Kodun her satırı bir geçişi anlatır; hiçbir satır sonucu anlatmaz.

**Arayüz çerçevesi (framework)**, bu tanımı tersine çeviren altyapıdır. Yazılan şey, verili
bir durumda arayüzün **ne olması gerektiğidir**; iki durum arasındaki farkı bulmak ve
ağaca uygulamak çerçevenin işidir. Çerçeve karşılığında dört sorumluluk devralır:

- durumdan görünüm tanımı üretmek,
- iki görünüm tanımı arasındaki farkı hesaplamak,
- farkı ağaca en az işlemle yazmak,
- kaldırılan parçaların dinleyicilerini ve kaynaklarını sökmek.

Bu kursta **çerçeve** sözcüğü yalnızca bu anlamda kullanılır; Ağ Modelleri ve Protokoller
kursundaki bağlantı katmanı çerçevesiyle ve gömülü içerik çerçevesiyle ilgisi yoktur.

Devir bedelsiz değildir. Fark hesabı çalışma zamanında yapılır ve maliyeti vardır; çerçeve
kendi kurallarını dayatır; hata ayıklarken araya bir katman girer. Bu bedelin ne zaman
ödendiği kursun son konusunda ölçülür. Kazanç ise bu derste sayılan şeydir: güncelleme
yolları ortadan kalkar, çünkü artık tek bir yol vardır.

#### Özet

- Elle güncelleme her parça için ilk oluşturma, güncelleme ve sökme kodunu ayrı ayrı
  yazmayı gerektirir; üçünün aynı görünümü betimlediğini hiçbir şey denetlemez.
- Güncelleme kodunun büyüklüğü parça sayısıyla değil, durum alanı ile parça arasındaki
  bağlantı sayısıyla artar; bu sayı en kötü durumda alan ve parça sayısının çarpımıdır.
- Ağaç bir depo değil çıktıdır: dizgi dışında tip saklamaz, görünmeyen durumu hiç saklamaz.
- Kayma, aynı metnin birden çok yerde hesaplanmasından doğar; kendini onaran biçimi onu
  yeniden üretilmesi zor bir hataya dönüştürür.
- Liste güncellemesi elle yazıldığında sıra, kimlik ve temizlik sorunları eklenir.
- Arayüz çerçevesi, "nasıl değiştirileceği" tanımını "ne olması gerektiği" tanımıyla
  değiştirir ve fark hesabını üstlenir.

#### Sonraki Adım

Çerçevenin devraldığı işin ilk maddesi, durumdan bir görünüm tanımı üretmektir. Bu, kulağa
geldiğinden daha katı bir gerekliliktir: tanımı üreten kodun aynı durum için her zaman aynı
sonucu vermesi, ağacın o anki hâline hiç bakmaması ve yazarken hiçbir şeyi değiştirmemesi
gerekir. Sonraki ders bu üretimi bir işlev olarak kurar, tek yönlü akışın ne anlama
geldiğini gösterir ve görünümün tamamını her seferinde yeniden üretmenin doğru ama pahalı
olduğunu ölçer.

### Bildirimsel Oluşturma

Source: https://academia.sh/tr/kurslar/bilesen-tabanli-gelistirme/bildirimsel-olusturma

Görünümü durumdan üreten saf fonksiyon; tek yönlü akışın döngüsü, saflığın üç koşulu, görünüm tanımının ağaçtan farkı ve tanımın tamamını yeniden kurmanın bedeli.

Önceki ders elle güncellemenin bedelini saydı: durum alanı başına birden çok güncelleme
yolu, aynı metnin birden çok yerde hesaplanması ve iki kopya arasındaki kaymanın kendini
onaran biçimi. Çıkan sonuç, arayüzün nasıl değiştirileceğinin değil ne olması gerektiğinin
yazılmasıydı.

Bu ders o yazımı bir işlev olarak kurar. İşlevin uyması gereken koşullar katıdır: aynı
durum için her zaman aynı sonucu vermeli, belge ağacının o anki hâline hiç bakmamalı ve
çalışırken hiçbir şeyi değiştirmemelidir. Bu üç koşul sağlandığında arayüz, durumun bir
sonucuna dönüşür.

#### Görünüm Bir İşlevdir

Bildirimsel model tek bir denklemle özetlenir: **görünüm, durumun bir işlevidir.** Girdi
uygulamanın o anki durumudur, çıktı arayüzün o durumdaki tam betimidir.

Çıktının belge ağacı olmadığı önemlidir. İşlev **görünüm tanımı** üretir: düğüm adları,
öznitelikler ve çocuklardan oluşan sade bir veri yapısı. Tanım hiçbir şey çizmez, hiçbir
şeye bağlı değildir ve okunabilir, karşılaştırılabilir, kopyalanabilir.

```js
// gorunum-islevi.mjs — durumdan gorunum tanimi ureten saf islev ve tek yonlu akis
const oge = (ad, ozn = {}, ...cocuklar) => ({ ad, ozn, cocuklar: cocuklar.flat() });

// Gorunum tanimi: dugumlerden olusan sade bir veri yapisi, agac degil.
function gorunum(durum) {
  const gorunen = durum.suzgec === "tumu"
    ? durum.olcumler
    : durum.olcumler.filter((o) => o.tur === durum.suzgec);
  return oge("section", { class: "pano" },
    oge("h2", {}, `Kuzey Yamaç — ${gorunen.length} / ${durum.olcumler.length} ölçüm`),
    oge("div", { class: "suzgec" },
      ["tumu", "sicaklik", "nem"].map((s) =>
        oge("button", { deger: s, secili: s === durum.suzgec }, s))),
    oge("ul", { class: "tablo" },
      gorunen.map((o) =>
        oge("li", { class: o.deger >= o.esik ? "rozet asim" : "rozet" },
          `${o.ad}: ${o.deger} ${o.birim}`))));
}

// Olaydan yeni durum ureten saf islev: eskisini degistirmez, yenisini dondurur.
function sonrakiDurum(durum, olay) {
  if (olay.tur === "suzgecSecildi") return { ...durum, suzgec: olay.deger };
  if (olay.tur === "olcumGeldi")
    return { ...durum, olcumler: [...durum.olcumler, olay.olcum] };
  return durum;
}

const BASLANGIC = {
  suzgec: "tumu",
  olcumler: [
    { ad: "Sıcaklık", tur: "sicaklik", deger: -4.2, birim: "°C", esik: 30 },
    { ad: "Bağıl nem", tur: "nem", deger: 72, birim: "%", esik: 90 },
  ],
};
const OLAYLAR = [
  { tur: "olcumGeldi", olcum: { ad: "Zemin ısısı", tur: "sicaklik", deger: 1.8, birim: "°C", esik: 30 } },
  { tur: "suzgecSecildi", deger: "sicaklik" },
  { tur: "olcumGeldi", olcum: { ad: "Çiy noktası", tur: "nem", deger: 95, birim: "%", esik: 90 } },
];

// Akisi bastan sona calistirir; her adimda durumu ve gorunum tanimini dondurur.
function akis(baslangic, olaylar) {
  let durum = baslangic;
  const adimlar = [{ durum, tanim: gorunum(durum) }];
  for (const olay of olaylar) {
    durum = sonrakiDurum(durum, olay);
    adimlar.push({ durum, tanim: gorunum(durum) });
  }
  return adimlar;
}

const yaz = (d, girinti = 0) => typeof d === "string"
  ? `${" ".repeat(girinti)}"${d}"`
  : [`${" ".repeat(girinti)}<${d.ad}${Object.entries(d.ozn)
      .map(([k, v]) => ` ${k}=${JSON.stringify(v)}`).join("")}>`,
     ...d.cocuklar.map((c) => yaz(c, girinti + 2))].join("\n");

const birinci = akis(BASLANGIC, OLAYLAR);
console.log(yaz(birinci.at(-1).tanim));

// Ayni baslangic ve ayni olay dizisi: uretilen tanim birebir ayni mi?
const ikinci = akis(BASLANGIC, OLAYLAR);
const esitMi = birinci.every((a, i) =>
  JSON.stringify(a.tanim) === JSON.stringify(ikinci[i].tanim));
console.log(`\nayni olay dizisi yeniden calistirildi → tanimlar ayni mi: ${esitMi}`);
console.log(`baslangic durumundaki olcum sayisi (uc olaydan sonra): ` +
  `${BASLANGIC.olcumler.length}`);
console.log(`adim basina gorunen olcum: ` +
  birinci.map((a) => a.tanim.cocuklar[2].cocuklar.length).join(", "));
```

```
<section class="pano">
  <h2>
    "Kuzey Yamaç — 2 / 4 ölçüm"
  <div class="suzgec">
    <button deger="tumu" secili=false>
      "tumu"
    <button deger="sicaklik" secili=true>
      "sicaklik"
    <button deger="nem" secili=false>
      "nem"
  <ul class="tablo">
    <li class="rozet">
      "Sıcaklık: -4.2 °C"
    <li class="rozet">
      "Zemin ısısı: 1.8 °C"

ayni olay dizisi yeniden calistirildi → tanimlar ayni mi: true
baslangic durumundaki olcum sayisi (uc olaydan sonra): 2
adim basina gorunen olcum: 2, 3, 2, 2
```

Üç sonuç okunmalıdır. Aynı olay dizisi yeniden çalıştırıldığında üretilen tanımlar
birebir aynıdır; bu, bir hatanın olay dizisi kaydedilerek yeniden üretilebileceği
anlamına gelir. Başlangıç durumu üç olaydan sonra da iki ölçümde kalmıştır; durum
değiştirilmemiş, her adımda yenisi üretilmiştir. Görünen ölçüm sayısı adım adım
değişmiştir, ama bu sayıyı hiçbir yerde kimse güncellememiştir — süzgeç seçiminin ve ölçüm
listesinin bir sonucudur.

#### Tek Yönlü Akış

Bu düzenin adı **tek yönlü veri akışı (one-way data flow)** dır. Döngü dört adımdır: durum
görünüm tanımını üretir, tanım ağaca uygulanır, kullanıcı etkileşimi bir olay üretir, olay
yeni bir durum üretir. Ok yönü hiçbir adımda tersine dönmez.

Tersine dönen yol **iki yönlü bağlama (two-way binding)** dır: arayüz parçası, okuduğu
değere doğrudan yazabilir. Yazım kısalır, ama iki sonuç doğar. Birincisi, değerin nereden
geldiği tek bir yere bakılarak anlaşılamaz; bir alanın değeri hem durumdan hem de kendi
üzerinden gelebilir. İkincisi, birbirini besleyen iki bağ arasında sonlanma sorusu doğar:
A, B'yi günceller, B, A'yı günceller. Tek yönlü akışta bu soru sorulmaz, çünkü durumu
değiştiren tek bir kapı vardır.

Tek yönlü akışın bedeli, form alanı gibi doğal olarak kendi değerini taşıyan ögelerde
görülür. Alanın değeri durumdan gelir, kullanıcının yazdığı harf bir olaya dönüşür, olay
yeni durumu üretir ve alan yeni değeri durumdan alır. Yol uzundur; kazancı, alanın
değerini durumdan başka hiçbir şeyin belirlememesidir.

#### Saflığın Üç Koşulu

Görünüm işlevi, Programlama Temelleri kursunda **saf fonksiyon (pure function)** adıyla
tanımlanan koşulu sağlamalıdır; bu kursta karşılığı **saf fonksiyon**dur. Koşul, arayüz
bağlamında üç somut yasak demektir.

**Aynı girdi aynı çıktı.** İşlevin içinde geçerli saati okumak, rastgele sayı üretmek ya da
bir sayaç artırmak, aynı durumun iki farklı tanım üretmesine yol açar. Bu değerlere
gerçekten gerek varsa, işlevin içinde üretilmez; durumun bir alanı hâline getirilir ve
dışarıda hesaplanır. "Son ölçüm iki dakika önce" metni, işlevin içinde okunan saatle değil,
durumdaki bir zaman damgasıyla hesaplanır.

**Ağaca bakmama.** İşlev, o anki belge ağacından bir değer okuyup kararını ona
dayandıramaz. Ağaçtan okumak, önceki dersteki iki kopya sorununu geri getirir: işlev kendi
ürettiği çıktıyı girdi olarak kullanmış olur. Ölçülen bir değere gerek varsa — bir kutunun
genişliği gibi — bu değer ölçülür, duruma yazılır ve bir sonraki oluşturmada girdi olarak
kullanılır.

**Yan etkisizlik.** İşlev çalışırken ağ isteği başlatmaz, depolamaya yazmaz,
zamanlayıcı kurmaz. Nedeni yalnızca temizlik değildir: bir görünüm tanımı üretildikten
sonra kullanılmayabilir, aynı durum için birden çok kez üretilebilir ya da başka bir
sırayla üretilebilir. Yan etki taşıyan bir işlev bu durumlarda öngörülemez davranır.

#### Tanım Ağaç Değildir

Tanımın ucuz, ağacın pahalı olması bu modelin çalışmasının nedenidir.

Görünüm tanımı, birkaç nesne ve dizidir; üretilmesi bellek ayırmaktan ibarettir. Belge
ağacındaki bir düğüm ise tarayıcının biçem çözümlemesine, yerleşim hesabına ve boyamaya
giren bir kayıttır. Bu yüzden tanım her durum değişiminde yeniden üretilebilir, ama ağaç
yeniden kurulamaz.

```js
// tam-yenileme.mjs — tanimin tamamini yeniden kurmanin bedeli
const oge = (ad, ozn = {}, ...cocuklar) => ({ ad, ozn, cocuklar: cocuklar.flat() });

function gorunum(durum) {
  return oge("section", { class: "pano" },
    oge("h2", {}, `Kuzey Yamaç — ${durum.olcumler.length} ölçüm`),
    oge("input", { ad: "arama", deger: durum.arama }),
    oge("ul", { class: "tablo" },
      durum.olcumler.map((o) =>
        oge("li", { class: "rozet" }, `${o.ad}: ${o.deger} ${o.birim}`))));
}

// Gercek agac dugumu: kimligi olan, olusturulmasi maliyetli nesne.
let uretilen = 0;
const yenidugum = (tanim) => {
  uretilen++;
  if (typeof tanim === "string") return { metin: tanim, kimlik: uretilen };
  return {
    ad: tanim.ad, ozn: { ...tanim.ozn }, kimlik: uretilen,
    cocuklar: tanim.cocuklar.map(yenidugum),
  };
};

let yazilanAlan = 0;
// En az islem: tur ayni ise dugum korunur, yalnizca degisen alan yazilir.
function uygula(dugum, tanim) {
  if (typeof tanim === "string") {
    if (!dugum || dugum.metin === undefined) return yenidugum(tanim);
    if (dugum.metin !== tanim) { dugum.metin = tanim; yazilanAlan++; }
    return dugum;
  }
  if (!dugum || dugum.ad !== tanim.ad) return yenidugum(tanim);
  for (const [k, v] of Object.entries(tanim.ozn))
    if (dugum.ozn[k] !== v) { dugum.ozn[k] = v; yazilanAlan++; }
  dugum.cocuklar = tanim.cocuklar.map((c, i) => uygula(dugum.cocuklar[i], c));
  return dugum;
}

const OLCUMLER = Array.from({ length: 12 }, (_, i) => ({
  ad: `Sensör ${String(i + 1).padStart(2, "0")}`, deger: 10 + i, birim: "°C",
}));
const ONCE = { arama: "", olcumler: OLCUMLER };
const SONRA = {
  arama: "",
  olcumler: OLCUMLER.map((o, i) => (i === 6 ? { ...o, deger: 17.5 } : o)),
};

// Ilk cizim: her iki yolda da agac sifirdan kurulur.
uretilen = 0;
const agacA = yenidugum(gorunum(ONCE));
const agacB = yenidugum(gorunum(ONCE));
const ilkMaliyet = uretilen / 2;

// Kullanici arama alanina yaziyor; bu bilgi agacta, tanimda degil.
const alanBul = (d) => d.ad === "input" ? d : d.cocuklar?.map(alanBul).find(Boolean);
alanBul(agacA).odakli = true;
alanBul(agacB).odakli = true;

// A yolu: tanimin tamami yeniden kuruluyor.
uretilen = 0;
const yeniA = yenidugum(gorunum(SONRA));
console.log(`ilk cizim                    : ${ilkMaliyet} dugum uretildi`);
console.log(`tam yeniden kurma            : ${uretilen} dugum uretildi, ` +
  `odak korundu mu: ${alanBul(yeniA).odakli === true}`);

// B yolu: yeni tanim var olan agaca uygulaniyor.
uretilen = 0; yazilanAlan = 0;
uygula(agacB, gorunum(SONRA));
console.log(`tanimi var olan agaca uygulama: ${uretilen} dugum uretildi, ` +
  `${yazilanAlan} alan yazildi, odak korundu mu: ${alanBul(agacB).odakli === true}`);
```

```
ilk cizim                    : 29 dugum uretildi
tam yeniden kurma            : 29 dugum uretildi, odak korundu mu: false
tanimi var olan agaca uygulama: 0 dugum uretildi, 1 alan yazildi, odak korundu mu: true
```

Tek bir ölçümün değeri değişmiştir. Ağacı baştan kurmak yirmi dokuz düğüm üretir; tanımı
var olan ağaca uygulamak tek bir alan yazar ve hiç düğüm üretmez.

Maliyet farkı ikincil sonuçtur. Asıl sonuç son sütundadır: **ağaçta yaşayan ve tanımda
bulunmayan durum**, yeniden kurmayla birlikte kaybolur. Odak bunlardan yalnızca biridir;
kaydırma konumu, seçili metin aralığı, süren geçişler, medya ögesinin oynatma konumu ve
gölge ağacın içindeki her şey aynı gruptadır. Görünüm tanımı bunların hiçbirini taşımaz,
çünkü hiçbiri durumun parçası değildir.

#### Yeniden Çizim ile Uygulamanın Ayrımı

Buradan kursun geri kalanını taşıyan bir ayrım çıkar.

**Yeniden çizim**, görünüm işlevinin yeniden çalıştırılmasıdır. Ucuzdur ve yalnızca yeni
bir tanım üretir; ekranda hiçbir şeyi değiştirmez.

**Uygulama**, yeni tanım ile ağacın karşılaştırılıp aradaki farkın yazılmasıdır. Pahalı
olan budur ve en aza indirilmesi gereken de budur.

"Bildirimsel model her şeyi yeniden kurar" yanılgısı bu ikisini birbirine karıştırır.
Yeniden çizilen şey tanımdır; ağaç yeniden kurulmaz, yamanır. Yukarıdaki `uygula` işlevi
bu yamamanın en yalın biçimidir ve bir eksiği vardır: çocukları yalnızca sıralarına göre
eşler. Listeye baştan bir kayıt eklendiğinde bütün eşleşmeler kayar. Bu eksiğin adı ve
çözümü konunun son iki dersindedir.

#### Özet

- Bildirimsel modelde görünüm, durumun bir işlevidir; işlevin çıktısı belge ağacı değil,
  sade veriden oluşan bir görünüm tanımıdır.
- Tek yönlü akış dört adımlıdır: durum, tanım, olay, yeni durum. İki yönlü bağlama bu
  döngüye geri kenar ekler ve değerin kaynağını belirsizleştirir.
- Görünüm işlevi saf olmalıdır: aynı girdiye aynı çıktı, ağaçtan okuma yok, yan etki yok.
  Saat ve ölçülmüş değerler işlevin içinde üretilmez, duruma yazılır.
- Tanımın üretilmesi ucuz, ağaç düğümünün üretilmesi pahalıdır; model bu asimetri üzerine
  kuruludur.
- Ağacı baştan kurmak, tanımda bulunmayan durumu — odak, kaydırma, seçim, süren geçiş —
  yok eder; tanımı var olan ağaca uygulamak korur.
- Yeniden çizim tanımı üretir, uygulama farkı ağaca yazar; iki işlem ayrı maliyetlerdir.

#### Sonraki Adım

Bu dersin görünüm işlevi tek parçaydı: panonun tamamını tek bir işlev betimledi. On beş
rozet, bir tablo ve bir süzgeç paneli aynı gövdeye sığdığında bu işlev okunamaz hâle gelir
ve hiçbir parçası tek başına yeniden kullanılamaz. Sonraki ders işlevi parçalara ayırır ve
her parçaya bir sözleşme verir: dışarıdan aldığı girdi, dışarıya bildirdiği olay ve
yalnızca kendini ilgilendiren yerel durum.

### Bileşen Kavramı

Source: https://academia.sh/tr/kurslar/bilesen-tabanli-gelistirme/bilesen-kavrami

Görünüm işlevinin parçalara ayrılması; girdi özelliği, çıktı geri çağrısı ve yerel durumdan oluşan sözleşme, bileşen örneğinin kimliği, yeniden çizimin yayıldığı alan ve durumun hangi bileşende durduğu.

Önceki dersin görünüm işlevi panonun tamamını tek gövdede betimliyordu. On beş rozet, bir
ölçüm tablosu ve bir süzgeç paneli aynı işleve sığdığında iki şey birden kaybolur: gövde
okunamaz hâle gelir ve hiçbir parçası tek başına yeniden kullanılamaz.

Bu ders işlevi parçalara ayırır. Ayrımın birimine **bileşen (component)** denir: kendi
görünüm tanımını üreten, dışarıyla üç tanımlı kanaldan konuşan bir işlev.

#### Bileşenin Üç Kanalı

Bir bileşenin dış dünyayla ilişkisi üç kanalla sınırlıdır.

**Girdi özelliği (prop)**, ebeveynin çocuğa verdiği değerdir. Bağlam açıkken kısaca
**özellik** de denir. Yönü tek yönlüdür ve okunurdur: çocuk aldığı değeri değiştiremez. Ölçüm rozetinin girdisi ölçümün adı, değeri,
birimi ve eşiğidir.

**Çıktı**, çocuğun ebeveyne bildirdiği olaydır. Çocuk bir karar vermez, olan biteni
yukarıya bildirir; kararı durumu tutan taraf verir. Süzgeç paneli hangi seçeneğe
tıklandığını bildirir, hangi süzgecin uygulanacağına karar vermez. Bildirimin taşıyıcısı,
girdi olarak verilen bir **geri çağrıdır**.

**Yerel durum (local state)**, yalnızca bileşenin kendisini ilgilendiren ve dışarıdan
görünmeyen değerdir. Rozetin ayrıntı bölümünün açık olup olmadığı buna örnektir: hiçbir
başka parça bu bilgiye gerek duymaz.

Sözleşmenin tamamı bu üç kanaldır. Bileşenin ebeveynine ulaşan başka bir yolu, kardeşine
doğrudan erişimi ya da ağaçtan okuduğu gizli bir değeri yoktur.

#### Bileşen Ağacı ve Örnek Kimliği

Bileşenler birbirini kullanır ve bir ağaç oluşturur. Bu ağaç belge ağacı değildir: her bileşen
bir belge düğümüne karşılık gelmek zorunda değildir ve tek bir bileşen onlarca düğüm üretebilir.

Ağaçtaki her konuma bir **bileşen örneği** karşılık gelir. Örnek, yerel durumun yaşadığı
yerdir ve yeniden çizimler arasında korunur.

```js
// bilesen-calisma-zamani.mjs — girdi, cikti ve yerel durumu olan kucuk bir bilesen modeli
const oge = (ad, ozn = {}, ...cocuklar) =>
  ({ tur: "oge", ad, ozn, cocuklar: cocuklar.flat() });
const kur = (islev, girdi = {}) => ({ tur: "bilesen", islev, girdi });

const ornekler = new Map();          // kimlik → { yerel, islev, girdi }
let sayac = new Map();               // bilesen adi → cizim sayisi

function oturum(kimlik) {
  const kayit = ornekler.get(kimlik);
  return {
    // Yerel durum ornege aittir; ayarlanmasi yalnizca bu ornegi yeniden cizer.
    yerel(ad, baslangic) {
      if (!kayit.yerel.has(ad)) kayit.yerel.set(ad, baslangic);
      return [kayit.yerel.get(ad), (yeni) => {
        kayit.yerel.set(ad, yeni);
        cizBilesen(kimlik, kayit.islev, kayit.girdi);
      }];
    },
  };
}

function cizBilesen(kimlik, islev, girdi) {
  if (!ornekler.has(kimlik)) ornekler.set(kimlik, { yerel: new Map() });
  const kayit = ornekler.get(kimlik);
  kayit.islev = islev; kayit.girdi = girdi;
  sayac.set(islev.name, (sayac.get(islev.name) ?? 0) + 1);
  return cizDugum(islev(Object.freeze(girdi), oturum(kimlik)), kimlik);
}

function cizDugum(tanim, yol) {
  if (typeof tanim === "string") return tanim;
  if (tanim.tur === "bilesen")
    return cizBilesen(`${yol}/${tanim.islev.name}`, tanim.islev, tanim.girdi);
  return { ad: tanim.ad, ozn: tanim.ozn,
    cocuklar: tanim.cocuklar.map((c, i) => cizDugum(c, `${yol}/${i}`)) };
}

// --- Bilesenler -------------------------------------------------------------
// Girdi: deger, birim, asim. Cikti: yok. Yerel durum: ayrinti acik mi.
function OlcumRozeti(girdi, { yerel }) {
  const [acik, ayarla] = yerel("acik", false);
  return oge("div", { class: girdi.asim ? "rozet asim" : "rozet", ac: () => ayarla(!acik) },
    `${girdi.ad}: ${girdi.deger} ${girdi.birim}`,
    acik ? oge("small", {}, `eşik ${girdi.esik} ${girdi.birim}`) : "");
}

// Girdi: secenekler, secili. Cikti: secildi geri cagrisi.
function SuzgecPaneli(girdi) {
  return oge("div", { class: "suzgec" }, girdi.secenekler.map((s) =>
    oge("button", { secili: s === girdi.secili, tikla: () => girdi.secildi(s) }, s)));
}

function OlcumTablosu(girdi) {
  return oge("ul", { class: "tablo" }, girdi.olcumler.map((o) =>
    kur(OlcumRozeti, { ...o, asim: o.deger >= o.esik })));
}

function Pano(girdi) {
  const gorunen = girdi.suzgec === "tumu"
    ? girdi.olcumler : girdi.olcumler.filter((o) => o.tur === girdi.suzgec);
  return oge("section", { class: "pano" },
    oge("h2", {}, `Kuzey Yamaç — ${gorunen.length} / ${girdi.olcumler.length} ölçüm`),
    kur(SuzgecPaneli, { secenekler: ["tumu", "sicaklik", "nem"],
      secili: girdi.suzgec, secildi: girdi.suzgecSecildi }),
    kur(OlcumTablosu, { olcumler: gorunen }));
}

// --- Calistirma -------------------------------------------------------------
const OLCUMLER = [
  { ad: "Sıcaklık", tur: "sicaklik", deger: -4.2, birim: "°C", esik: 30 },
  { ad: "Bağıl nem", tur: "nem", deger: 72, birim: "%", esik: 90 },
  { ad: "Zemin ısısı", tur: "sicaklik", deger: 1.8, birim: "°C", esik: 30 },
  { ad: "Çiy noktası", tur: "nem", deger: 95, birim: "%", esik: 90 },
];
let durum = { suzgec: "tumu", olcumler: OLCUMLER };
const kokCiz = () => cizBilesen("kok", Pano, {
  ...durum,
  suzgecSecildi: (s) => { durum = { ...durum, suzgec: s }; kokCiz(); },
});
const bildir = (etiket) => {
  console.log(`${etiket.padEnd(30)} ${[...sayac].map(([a, n]) => `${a} ${n}`).join(", ")}`);
  sayac = new Map();
};

kokCiz();
bildir("ilk cizim");

// Ilk rozetin yerel durumu degisiyor: yalnizca o ornek yeniden cizilir.
const ROZET = [...ornekler.keys()].find((k) => k.endsWith("OlcumRozeti"));
console.log(`ilk rozet ornegi: ${ROZET}`);
oturum(ROZET).yerel("acik", false)[1](true);
bildir("bir rozetin yerel durumu");

// Panonun durumu degisiyor: kokten baslayan yeniden cizim.
ornekler.get("kok").girdi.suzgecSecildi("sicaklik");
bildir("panonun durumu");

console.log(`\nsuzgecten sonra o rozetin yerel durumu: ` +
  `${ornekler.get(ROZET).yerel.get("acik")}`);
console.log(`kayitli bilesen ornegi sayisi: ${ornekler.size}`);
```

```
ilk cizim                      Pano 1, SuzgecPaneli 1, OlcumTablosu 1, OlcumRozeti 4
ilk rozet ornegi: kok/2/OlcumTablosu/0/OlcumRozeti
bir rozetin yerel durumu       OlcumRozeti 1
panonun durumu                 Pano 1, SuzgecPaneli 1, OlcumTablosu 1, OlcumRozeti 2

suzgecten sonra o rozetin yerel durumu: true
kayitli bilesen ornegi sayisi: 7
```

#### Yeniden Çizimin Yayıldığı Alan

Çıktının dört yeri okunmalıdır.

Birincisi, **örneğin kimliği ağaçtaki konumdan türetilir**. `kok/2/OlcumTablosu/0/OlcumRozeti`
yolu, panonun üçüncü çocuğundaki tablonun birinci rozetini gösterir. Yerel durum bu
kimliğe bağlanır; kimlik değişirse durum başka bir örneğe ait olur. Bu bağın kırıldığı yer
listelerdir ve konunun beşinci dersinin konusudur.

İkincisi, **yerel durum yalnızca kendi örneğini yeniden çizer**. Rozetin ayrıntısı
açıldığında tek bir çizim yapılmıştır; pano, süzgeç paneli ve tablo hiç çalışmamıştır.
Durumu olabildiğince aşağıda tutmanın somut karşılığı budur.

Üçüncüsü, **yukarıdaki durum aşağıyı tümüyle yeniden çizer**. Süzgeç seçildiğinde kökten
başlayan çizim beş bileşeni çalıştırmıştır. Bu, kalıtsal bir maliyet değil, saflığın
sonucudur: girdisi değişmiş olabilecek her çocuğun yeniden çalıştırılması gerekir. Çizim
sayısı ikide kalmıştır çünkü süzgeç görünen ölçümü ikiye düşürmüştür.

Dördüncüsü, **yerel durum yeniden çizimden sağ çıkmıştır**. Rozet yeniden çalıştırılmış ama
`acik` değeri korunmuştur; durum işlevin içinde değil, örnekte yaşar.

Son satır bir eksiği de gösterir: süzgeç iki rozeti ağaçtan çıkardığı hâlde yedi örnek
kayıtlı kalmıştır. Ağaçtan çıkan bileşenin örneğini ve kaynaklarını kaldırmak, çerçevenin
sökme sorumluluğudur; bu model onu yapmaz.

#### Girdi Yazılamaz, Durumun Bir Sahibi Vardır

Sözleşmenin iki maddesi sıkça ihlal edilir: çocuk girdisine yazar, ya da ortak olması
gereken durum çocuğun içinde tutulur.

```js
// sozlesme.mjs — girdinin degistirilemezligi ve durumun sahibi
// 1) Girdi yalnizca okunur. Cerceve girdiyi donarak sozlesmeyi zorlar.
function OlcumRozeti(girdi) {
  girdi.deger = Math.round(girdi.deger);   // sozlesme ihlali
  return `${girdi.ad}: ${girdi.deger}`;
}
try {
  OlcumRozeti(Object.freeze({ ad: "Sıcaklık", deger: -4.2 }));
} catch (hata) {
  console.log(`girdiye yazma: ${hata.constructor.name} — ${hata.message}`);
}

// 2) "En fazla bir rozet secili" degismezi durumun nerede durduguna baglidir.
const ADLAR = ["Sıcaklık", "Bağıl nem", "Rüzgâr"];
const TIKLAMALAR = ["Sıcaklık", "Bağıl nem", "Rüzgâr"];

// A) Durum her rozetin icinde: rozet kendi seciliigini kendisi cevirir.
const rozetlerA = ADLAR.map((ad) => ({ ad, secili: false }));
const rozetA = (rozet) => { rozet.secili = !rozet.secili; };
for (const ad of TIKLAMALAR) rozetA(rozetlerA.find((r) => r.ad === ad));

// B) Durum ortak atada: rozet yalnizca olayi yukari bildirir.
let pano = { seciliAd: null };
const rozetB = (girdi) => girdi.secildi(girdi.ad);
for (const ad of TIKLAMALAR)
  rozetB({ ad, secili: pano.seciliAd === ad,
    secildi: (a) => { pano = { seciliAd: pano.seciliAd === a ? null : a }; } });

const seciliA = rozetlerA.filter((r) => r.secili).map((r) => r.ad);
const seciliB = pano.seciliAd ? [pano.seciliAd] : [];
console.log(`\ntiklama dizisi: ${TIKLAMALAR.join(" → ")}`);
console.log(`A) durum rozetlerde  → secili: [${seciliA}] (${seciliA.length} adet)`);
console.log(`B) durum ortak atada → secili: [${seciliB}] (${seciliB.length} adet)`);
console.log(`degismez ("en fazla bir secili") A: ${seciliA.length <= 1}, ` +
  `B: ${seciliB.length <= 1}`);

// A'da pano baslikta "1 rozet secili" yazamaz: sayi rozetlerin icinde dagilmistir.
console.log(`\npanonun okuyabildigi secim sayisi — A: yok, B: ${seciliB.length}`);
```

```
girdiye yazma: TypeError — Cannot assign to read only property 'deger' of object '#<Object>'

tiklama dizisi: Sıcaklık → Bağıl nem → Rüzgâr
A) durum rozetlerde  → secili: [Sıcaklık,Bağıl nem,Rüzgâr] (3 adet)
B) durum ortak atada → secili: [Rüzgâr] (1 adet)
degismez ("en fazla bir secili") A: false, B: true

panonun okuyabildigi secim sayisi — A: yok, B: 1
```

Birinci bölüm, girdiye yazmanın neden yasak olduğunu gösterir. Yazma başarılı olsaydı,
değişiklik ebeveynin nesnesine geçerdi ve ebeveyn durumunu değiştirmediği için yeniden
çizim tetiklenmezdi: durum değişmiş, görünüm değişmemiş olurdu. Bu, ilk dersteki kaymanın
aynısıdır. Girdi nesnesinin dondurulması ihlali sessiz bir soruna değil, konumu belli bir
hataya çevirir.

İkinci bölüm, durumun yerinin bir tercih değil bir doğruluk sorunu olduğunu gösterir.
Seçili olma durumu rozetlerin içine dağıtıldığında "en fazla bir rozet seçili" değişmezini
koruyacak bir yer kalmaz; üç tıklama üç seçili rozet üretir. Ortak atada tutulduğunda aynı
tıklama dizisi tek bir seçim bırakır, çünkü değeri yazan tek bir yer vardır.

Son satır ikinci sonucu ekler: dağıtılmış durum **okunamaz** da. Başlıkta kaç rozetin
seçili olduğunu yazmak isteyen pano, A düzeninde bu sayıya erişemez.

#### Durum Nerede Durmalı

Karar iki ölçüte iner.

Bir değeri **yalnızca bir bileşen** okuyup yazıyorsa, o bileşenin yerel durumudur. Aşağıda
tutulan durum daha küçük bir alanı yeniden çizer ve bileşeni tek başına kullanılabilir
kılar.

Bir değeri **birden çok bileşen** okuyor ya da yazıyorsa, o değer bunların **en yakın ortak
atasına** taşınır. Bu taşımaya **durumun yukarı taşınması (lifting state up)** denir. Ata
değeri tutar, çocuklara girdi olarak verir, çocukların bildirdiği olaylarla günceller.

İki ölçüt de aynı ilkeye dayanır: her değerin tek bir sahibi olmalıdır. Aynı bilginin iki
yerde tutulması, ilk derste ağaç ile durum arasında görülen kaymanın bileşenler arasındaki
biçimidir.

Yükseltmenin bir bedeli vardır: değer ile onu kullanan bileşen arasındaki her katman
girdiyi taşımak zorunda kalır. Ara katmanların yalnızca aktarmak için taşıdığı girdiler
biriktiğinde, ağaç boyunca değer sağlamanın başka bir yolu gerekir; bu yol kursun ilerleyen
konusunda ele alınır.

#### Özet

- Bileşen, dış dünyayla üç kanaldan konuşan bir görünüm işlevidir: okunur girdi özelliği,
  yukarı bildirilen çıktı ve dışarıdan görünmeyen yerel durum.
- Bileşen ağacı belge ağacından ayrıdır; ağaçtaki her konuma karşılık gelen bileşen örneği,
  yerel durumun yaşadığı ve yeniden çizimler arasında korunduğu yerdir.
- Yerel durum yalnızca kendi örneğini yeniden çizer; üstteki bir durum, altındaki bütün
  bileşenleri yeniden çalıştırır.
- Girdiye yazmak, durumu değiştirip yeniden çizim tetiklemediği için kaymaya yol açar;
  girdinin dondurulması ihlali erken yakalar.
- Birden çok bileşeni ilgilendiren durum en yakın ortak atada tutulur; dağıtılmış durum hem
  değişmezi koruyamaz hem de yukarıdan okunamaz.
- Her değerin tek bir sahibi olmalıdır; yükseltmenin bedeli, ara katmanların girdiyi
  yalnızca aktarmak için taşımasıdır.

#### Sonraki Adım

Bu dersin bileşenleri görünüm tanımını işlev çağrılarıyla kurdu: her öge bir çağrı, her
öznitelik bir nesne alanı. Bir ölçüm tablosunun bütün yapısı bu biçimde yazıldığında
işaretlemenin şekli kaybolur; iç içe geçmiş çağrılar arasında hangi ögenin nerede
kapandığını okumak güçleşir. Sonraki ders işaretlemenin kendi söz diziminde yazılıp veriye
bağlanmasını ele alır: metnin içine ifade yerleştirmek, öznitelik değerini bir ifadeden
almak, olayı bir işleve bağlamak ve bunların hangi noktada kaçırma gerektirdiğini
belirlemek.

### Şablon ve İfade Sözdizimi

Source: https://academia.sh/tr/kurslar/bilesen-tabanli-gelistirme/sablon-ve-ifade-sozdizimi

İşaretlemenin veriye bağlanması; şablonun bir kez derlenip çok kez çalıştırılması, sabit ve devingen parçaların ayrılması, ifadelerin kapsamı ile sınırı, metin kaçırma ve öznitelik–özellik bağlaması ayrımı.

Önceki dersin bileşenleri görünüm tanımını iç içe işlev çağrılarıyla kurdu. Bir ölçüm
tablosunun bütün yapısı bu biçimde yazıldığında işaretlemenin şekli kaybolur: hangi ögenin
nerede kapandığı, hangi özniteliğin hangi ögeye ait olduğu çağrı parantezleri arasında
okunur hâle gelmez.

Bu ders işaretlemenin kendi söz diziminde yazılıp veriye bağlanmasını ele alır. Kurulacak
mekanizmanın adı **şablon (template)** dur: içinde ifade taşıyan bir işaretleme metni ve
onu görünüm tanımı üreten bir işleve çeviren bir derleme adımı.

#### İki Yazım Ailesi

İşaretlemeyi programa sokmanın iki yolu vardır ve çerçeve aileleri bu ayrımla bölünür.

Birinci yolda **işaretleme dilin içine gömülür**: programlama dilinin söz dizimi genişletilir ve
işaretleme dilin bir ifadesi hâline gelir. İfade yeri diye ayrı bir kavram yoktur; işaretlemenin
içindeki her şey zaten dilin ifadesidir. Kazanç, dilin bütün olanaklarının doğrudan
kullanılabilmesidir.

İkinci yolda **şablon ayrı bir metindir**: işaretlemeye özel imler eklenir ve bu metin
derlenerek işleve çevrilir. Şablon dili dilden küçüktür; yalnızca ifadelere ve birkaç
yönergeye izin verir. Kazanç, küçük dilin çözümlenebilir olmasıdır: derleyici hangi
parçanın sabit hangisinin devingen olduğunu bilir ve bu bilgiyle iş yapabilir.

İki yol da aynı yere varır: durumdan görünüm tanımı üreten bir işlev. Fark, bu işlevin ne
zaman ve ne kadar bilgiyle üretildiğidir.

#### Şablonun Derlenmesi

Aşağıdaki derleyici ikinci yolun küçük bir örneğidir. Şablon bir kez çözümlenir ve sonuç bir
komut ağacına dönüşür; her oluşturmada yalnızca bu ağaç çalıştırılır.

```js
// sablon-derleyici.mjs — sablon metnini gorunum ureten bir isleve derler
const SABLON = `
<div class="rozet" :durum="deger >= esik ? 'asim' : 'normal'" @tikla="ac">
  <span class="ad">{{ad}}</span>
  <span class="deger">{{deger}} {{birim}}</span>
</div>`.trim();

// 1) Sozcuksel cozumleme: etiketler ve metin parcalari.
// Etiket eslemesi tirnak icindeki ">" karakterini atlar.
const ETIKET = /<\/[a-z][\w-]*>|<[a-z][\w-]*(?:\s+[:@]?[\w-]+(?:="[^"]*")?)*\s*>|[^<]+/g;
const parcala = (metin) => metin.match(ETIKET) ?? [];

// 2) Metni sabit dilim ve ifade olarak ayirir: "{{ad}}: {{deger}}" → 4 parca.
function metniAyir(metin) {
  const parcalar = [];
  let son = 0;
  for (const eslesme of metin.matchAll(/\{\{([^}]+)\}\}/g)) {
    if (eslesme.index > son)
      parcalar.push({ tur: "sabit", deger: metin.slice(son, eslesme.index) });
    parcalar.push({ tur: "ifade", kaynak: eslesme[1].trim() });
    son = eslesme.index + eslesme[0].length;
  }
  if (son < metin.length) parcalar.push({ tur: "sabit", deger: metin.slice(son) });
  return parcalar;
}

// 3) Oznitelikleri uce ayirir: sabit, bagli (:) ve olay (@).
function oznitelikleriAyir(govde) {
  const sabit = {}, bagli = {}, olay = {};
  for (const [, ad, deger] of govde.matchAll(/([:@]?[\w-]+)="([^"]*)"/g)) {
    if (ad.startsWith(":")) bagli[ad.slice(1)] = deger;
    else if (ad.startsWith("@")) olay[ad.slice(1)] = deger;
    else sabit[ad] = deger;
  }
  return { sabit, bagli, olay };
}

// 4) Derleme: sablon bir kez cozumlenir, sonuc bir komut agacina donusur.
function derle(sablon, kapsamAdlari) {
  const degerlendir = (kaynak) =>
    new Function(...kapsamAdlari, `return (${kaynak});`);
  const yigit = [{ cocuklar: [] }];
  let sabitSayisi = 0, devingenSayisi = 0;

  for (const parca of parcala(sablon)) {
    if (parca.startsWith("</")) { yigit.pop(); continue; }
    if (parca.startsWith("<")) {
      const ad = parca.match(/^<([a-z]+)/)[1];
      const { sabit, bagli, olay } = oznitelikleriAyir(parca);
      sabitSayisi += Object.keys(sabit).length;
      devingenSayisi += Object.keys(bagli).length + Object.keys(olay).length;
      const dugum = {
        ad, sabit, cocuklar: [],
        bagli: Object.fromEntries(Object.entries(bagli)
          .map(([k, v]) => [k, degerlendir(v)])),
        olay: Object.fromEntries(Object.entries(olay)
          .map(([k, v]) => [k, degerlendir(v)])),
      };
      yigit.at(-1).cocuklar.push(dugum);
      yigit.push(dugum);
      continue;
    }
    if (!parca.trim()) continue;
    for (const p of metniAyir(parca.trim())) {
      if (p.tur === "sabit") { sabitSayisi++; yigit.at(-1).cocuklar.push(p.deger); }
      else { devingenSayisi++; yigit.at(-1).cocuklar.push(degerlendir(p.kaynak)); }
    }
  }
  console.log(`derleme: ${sabitSayisi} sabit parca, ${devingenSayisi} devingen parca`);
  return { kok: yigit[0].cocuklar[0], kapsamAdlari };
}

// 5) Calistirma: komut agaci + kapsam → gorunum tanimi.
function uret({ kok, kapsamAdlari }, kapsam) {
  const argumanlar = kapsamAdlari.map((ad) => kapsam[ad]);
  const cik = (d) => {
    if (typeof d === "string") return d;
    if (typeof d === "function") return String(d(...argumanlar));
    return {
      ad: d.ad,
      ozn: { ...d.sabit,
        ...Object.fromEntries(Object.entries(d.bagli)
          .map(([k, f]) => [k, f(...argumanlar)])),
        ...Object.fromEntries(Object.entries(d.olay)
          .map(([k, f]) => [`on:${k}`, f(...argumanlar).name || "islev"])) },
      cocuklar: d.cocuklar.map(cik),
    };
  };
  return cik(kok);
}

const KAPSAM = ["ad", "deger", "birim", "esik", "ac"];
const derlenmis = derle(SABLON, KAPSAM);

const yaz = (d, girinti = 0) => typeof d === "string"
  ? `${" ".repeat(girinti)}${JSON.stringify(d)}`
  : [`${" ".repeat(girinti)}<${d.ad}${Object.entries(d.ozn)
      .map(([k, v]) => ` ${k}=${JSON.stringify(v)}`).join("")}>`,
     ...d.cocuklar.map((c) => yaz(c, girinti + 2))].join("\n");

for (const olcum of [
  { ad: "Sıcaklık", deger: -4.2, birim: "°C", esik: 30, ac: () => {} },
  { ad: "Bağıl nem", deger: 94, birim: "%", esik: 90, ac: () => {} },
]) console.log(`\n${yaz(uret(derlenmis, olcum))}`);

// Ifade yalnizca bildirilen kapsam adlarini gorur.
try {
  new Function(...KAPSAM, "return (esikAsimSayisi);")("x", 0, "", 0, null);
} catch (hata) {
  console.log(`\nkapsamda olmayan ad: ${hata.constructor.name} — ${hata.message}`);
}
```

```
derleme: 4 sabit parca, 5 devingen parca

<div class="rozet" durum="normal" on:tikla="ac">
  <span class="ad">
    "Sıcaklık"
  <span class="deger">
    "-4.2"
    " "
    "°C"

<div class="rozet" durum="asim" on:tikla="ac">
  <span class="ad">
    "Bağıl nem"
  <span class="deger">
    "94"
    " "
    "%"

kapsamda olmayan ad: ReferenceError — esikAsimSayisi is not defined
```

Çıktının dört sonucu vardır.

**Derleme bir kez, çalıştırma çok kez.** Çözümleme satırı yalnızca bir kez yazılmıştır;
iki farklı ölçüm için görünüm üretilirken şablon metnine bir daha bakılmamıştır. On beş
rozet için de aynıdır. Şablonun ayrıştırılması, oluşturma maliyetinin parçası değildir.

**Sabit ve devingen parçalar ayrılmıştır.** Derleyici dört parçanın hiçbir duruma bağlı
olmadığını bilir. Bu bilgi bir sonraki derste maliyet düşürmek için kullanılır:
karşılaştırılması gerekmeyen parçalar baştan bellidir.

**İfade, bildirilen kapsamda değerlendirilir.** Şablondaki `deger` adı, bileşenin
kapsamındaki `deger` değişkenidir; kapsamda bulunmayan bir ad çalışma zamanında hata
üretir. Şablonun gördüğü adlar bileşenin verdiği adlarla sınırlıdır.

**Üç bağlama türü ayrı imlerle yazılmıştır.** Ön ekli olmayan öznitelik sabittir; iki nokta
ön eki değeri bir ifadeden alır; kuyruklu a ön eki bir olay bağlar. Ayrımın söz diziminde
görünür olması, derleyicinin her birine farklı davranmasını sağlar.

#### İfadelerin Sınırı

Şablon ifadeleri, oluşturma sırasında ve her oluşturmada çalışır. Bundan iki kural çıkar.

**İfade yan etki üretmemelidir.** Önceki dersin saflık koşulu şablona da uygulanır: içinde
durum değiştiren, istek başlatan ya da sayaç artıran bir ifade, oluşturma sayısına bağlı
davranır.

**İfade ucuz olmalıdır.** Şablonun içinde bir listeyi sıralamak ya da süzmek, her
oluşturmada yeniden hesaplanır. Bu hesabın bir kez yapılıp sonucunun saklanması, kursun
türetilmiş değerler dersinin konusudur.

Şablon dilinin ifadeyle sınırlı tutulması bu kuralları söz dizimi düzeyinde de destekler:
deyim yazılamayan bir dilde döngü kurmak, atama yapmak ya da hata yakalamak olanaksızdır.
Bu iş bileşenin gövdesine ait olur.

#### Metin, Öznitelik ve Olay Bağlaması

Üç bağlama türünün davranışı birbirinden ayrıdır ve karıştırıldıklarında iki tanıdık sorun
doğar.

```js
// baglama-turleri.mjs — metin kacirma ile oznitelik/ozellik baglamasinin farki
// 1) Metin yerlestirme kacirir; ham ekleme kacirmaz.
const kacir = (s) => String(s)
  .replace(/&/g, "&amp;").replace(/</g, "&lt;")
  .replace(/>/g, "&gt;").replace(/"/g, "&quot;");

const not = 'Sensör <b>şüpheli</b> okuma';            // forma girilmis serbest metin
console.log(`metin yerlestirme : <p>${kacir(not)}</p>`);
console.log(`ham ekleme        : <p>${not}</p>`);

const etiket = 'kar" veri-yetki="tam';                 // tirnak tasiyan deger
console.log(`\noznitelik (kacirilmis): <li tur="${kacir(etiket)}">`);
console.log(`oznitelik (kacirilmamis): <li tur="${etiket}">`);
const oznitelikSayisi = (s) => [...s.matchAll(/[\w-]+="/g)].length;
console.log(`uretilen oznitelik sayisi — kacirilmis: ` +
  `${oznitelikSayisi(`<li tur="${kacir(etiket)}">`)}, ` +
  `kacirilmamis: ${oznitelikSayisi(`<li tur="${etiket}">`)}`);

// 2) Oznitelik belgedeki dizgi, ozellik yasayan degerdir; kullanici yazinca ayrisirlar.
function alanKur() {
  const dugum = { ozn: {}, canliDeger: "", kullaniciDokundu: false };
  return {
    dugum,
    oznitelikYaz(deger) {
      dugum.ozn.deger = String(deger);
      if (!dugum.kullaniciDokundu) dugum.canliDeger = String(deger); // ilk deger
    },
    ozellikYaz(deger) { dugum.canliDeger = String(deger); },
    kullaniciYazar(metin) { dugum.kullaniciDokundu = true; dugum.canliDeger = metin; },
  };
}

for (const tur of ["oznitelik", "ozellik"]) {
  const alan = alanKur();
  const yaz = (v) => tur === "oznitelik" ? alan.oznitelikYaz(v) : alan.ozellikYaz(v);
  yaz("kar");                       // ilk cizim: durumdaki arama metni
  alan.kullaniciYazar("kar de");    // kullanici yaziyor
  yaz("kar derinliği");             // durum guncellendi, yeniden cizim
  console.log(`\n${tur} baglamasi → belgedeki deger: ` +
    `${JSON.stringify(alan.dugum.ozn.deger ?? null)}, ` +
    `alanda gorunen: ${JSON.stringify(alan.dugum.canliDeger)}`);
}
```

```
metin yerlestirme : <p>Sensör &lt;b&gt;şüpheli&lt;/b&gt; okuma</p>
ham ekleme        : <p>Sensör <b>şüpheli</b> okuma</p>

oznitelik (kacirilmis): <li tur="kar&quot; veri-yetki=&quot;tam">
oznitelik (kacirilmamis): <li tur="kar" veri-yetki="tam">
uretilen oznitelik sayisi — kacirilmis: 1, kacirilmamis: 2

oznitelik baglamasi → belgedeki deger: "kar derinliği", alanda gorunen: "kar de"

ozellik baglamasi → belgedeki deger: null, alanda gorunen: "kar derinliği"
```

**Metin yerleştirme kaçırır.** Kullanıcının girdiği metin metin olarak kalır; içindeki
işaretleme karakterleri Web'in Temelleri ve HTML kursundaki karakter başvurularına
çevrilir. Bu, şablon söz diziminin varsayılan davranışıdır ve seçilmiş bir güvenlik
kararıdır. Aynı şablon dilinin sunduğu **ham ekleme** yolu bu korumayı kapatır: değer
işaretleme olarak yorumlanır. Ham eklemenin girdisi hiçbir zaman kullanıcıdan gelmemelidir.

**Öznitelik bağlaması da kaçırır.** Kaçırılmamış bir değer tırnağı kapatıp yeni bir
öznitelik açar: örnekte tek öznitelik yerine iki öznitelik üretilmiştir. Şablon
derleyicisinin değeri koyduğu yeri bilmesi, doğru kaçırmayı seçebilmesinin nedenidir; aynı
değer metin içinde, öznitelik içinde ve adres değeri olarak farklı kurallarla kaçırılır.

**Öznitelik ile özellik aynı şey değildir.** DOM API'si dersinde kurulan ayrım şablonda bir
seçime dönüşür. Öznitelik yazmak belgedeki dizgiyi değiştirir; kullanıcı alana
dokunduktan sonra alanın yaşayan değerini artık izlemez. Çıktının son iki satırı bunu
gösterir: öznitelik yolu belgedeki değeri günceller ama alanda görünen metin kullanıcının
yazdığı hâlde kalır. Özellik yolu yaşayan değeri günceller.

Kural buradan çıkar: **görüntülenen değeri taşıyan bağlamalar özellik, belgedeki
üstveriyi taşıyanlar öznitelik olarak yazılır.** Mantıksal öznitelikler ayrı bir durumdur:
belgede varlık–yokluk taşırlar, program tarafında ise doğru–yanlış; bir mantıksal
özniteliği yanlış değerle yazmak onu kaldırmakla aynı şey değildir.

**Olay bağlaması bir işlev bağlar.** Şablondaki `@tikla="ac"` imi bir dizgi değil, kapsamda
bulunan bir işlev başvurusu üretir; çıktıda `on:tikla="ac"` olarak görünen şey odur.
Değerin dizgi olarak yorumlanması, işaretlemenin içine kod yazmak anlamına gelirdi.

#### Özet

- Şablon, işaretlemenin kendi söz diziminde yazılıp veriye bağlanmasıdır; derleme adımı onu
  görünüm tanımı üreten bir işleve çevirir.
- İşaretleme ya dilin içine gömülür ya da ayrı bir metin olarak derlenir; ikinci yolda
  derleyici sabit ve devingen parçaları ayırt edebilir.
- Şablon bir kez çözümlenir, üretilen komut ağacı her oluşturmada çalıştırılır; ayrıştırma
  oluşturma maliyetinin parçası değildir.
- Şablon ifadeleri bileşenin kapsamında değerlendirilir, yan etki üretmemeli ve ucuz
  olmalıdır; deyim yazılamaması bu kuralı söz dizimi düzeyinde destekler.
- Metin yerleştirme ve öznitelik bağlaması varsayılan olarak kaçırır; ham ekleme kaçırmaz
  ve girdisi kullanıcıdan gelmemelidir.
- Görüntülenen değeri taşıyan bağlamalar özellik, belgedeki üstveriyi taşıyanlar öznitelik
  olarak yazılır; olay bağlaması dizgi değil işlev başvurusu bağlar.

#### Sonraki Adım

Bu dersin şablonu tek bir rozeti betimledi ve yapısı her durumda aynı kaldı: aynı ögeler,
aynı sırada. Gerçek bir pano ise değişken yapılıdır — süzgeç hiçbir ölçümle eşleşmediğinde
tablo yerine bir boş durum iletisi görünür, ölçüm listesi büyüyüp küçülür, satırlar yeniden
sıralanır. Sonraki ders şablona koşul ve yineleme yönergelerini ekler ve asıl soruyu sorar:
liste değiştiğinde yeni tanımdaki hangi satırın ağaçtaki hangi düğüme karşılık geldiğini ne
belirler?

### Koşullu ve Listeli Oluşturma

Source: https://academia.sh/tr/kurslar/bilesen-tabanli-gelistirme/kosullu-ve-listeli-olusturma

Değişken yapılı görünümler; ögeyi üretmemek ile gizlemek arasındaki fark, liste güncellemesinde sıraya göre ve anahtara göre eşlemenin maliyeti, anahtarın seçim ölçütleri ve kimliğin korunması.

Önceki dersin şablonu her durumda aynı yapıyı üretti: aynı ögeler, aynı sırada. Gerçek bir
pano ise değişken yapılıdır. Süzgeç hiçbir ölçümle eşleşmediğinde tablo yerine bir ileti
görünür, ölçüm listesi büyür ve küçülür, satırlar yeniden sıralanır.

Bu ders şablona koşul ve yineleme ekler. İkinci yarısı, bildirimsel modelin en sık atlanan
sorusudur: yeni tanımdaki hangi satırın ağaçtaki hangi düğüme karşılık geldiğini ne
belirler?

#### Koşullu Oluşturma

Koşullu oluşturmanın iki biçimi vardır. Koşul sağlanmadığında ya **hiçbir şey üretilmez**,
ya da **başka bir dal üretilir**. İkincisinin özel bir hâli boş durumdur: liste boşken
listenin yerine bir açıklama üretilir.

Buradaki karar, ögeyi üretmemek ile üretip gizlemek arasındadır ve ikisi eş değildir.

Üretilmeyen öge belge ağacında yoktur: erişilebilirlik ağacında görünmez, içindeki form
alanları gönderilmez, içindeki görseller indirilmez, sekme sırasında yer almaz. Biçemle
gizlenen öge ağaçta durur; gizleme yöntemine göre erişilebilirlik ağacından çıkabilir de
çıkmayabilir de, ama düğümleri ve dinleyicileri bellekte kalmaya devam eder.

Ölçüt kullanım sıklığıdır. Sık açılıp kapanan ve kurulumu pahalı olan bir parça —
sekmelerden biri, ölçüm grafiği — üretilip gizlenir; böylece her açılışta yeniden
kurulmaz. Nadiren görünen ve büyük bir parça üretilmez; sayfanın ilk yükünü küçültmek
kazançtır.

Koşulun kendisi de bir kimlik sorusu doğurur. İki dal aynı konumda aynı tür ögeyi
üretiyorsa, çerçeve bunu "aynı öge, içeriği değişti" diye okur ve düğümü yeniden kullanır.
Bu, dersin sonunda ölçülen soruna yol açar.

#### Listeli Oluşturma ve Eşleştirme Sorunu

Listeli oluşturma, bir veri dizisinin her kaydı için bir görünüm tanımı üretmektir.
Üretmek kolaydır; sorun ikinci oluşturmada başlar. Elde iki liste vardır — ağaçtaki eski
düğümler ve yeni tanımdaki kayıtlar — ve hangisinin hangisiyle eşleşeceğine karar
verilmelidir.

İki eşleme kuralı vardır. **Sıraya göre eşleme** n. düğümü n. kayda bağlar. **Anahtara göre
eşleme**, her kaydın taşıdığı bir anahtarla düğümü bulur.

```js
// liste-eslemesi.mjs — siraya gore ve anahtara gore eslemenin urettigi islem sayisi
const ESKI = ["sicaklik", "nem", "ruzgar", "kar"];
const DURUMLAR = {
  "sona ekleme":   [...ESKI, "basinc"],
  "basa ekleme":   ["basinc", ...ESKI],
  "ortadan silme": ESKI.filter((a) => a !== "ruzgar"),
  "yeniden sirala": ["kar", "sicaklik", "ruzgar", "nem"],
};

// A) Siraya gore esleme: n. eski dugum n. yeni kayitla eslesir.
function siraylaEsle(eski, yeni) {
  const ortak = Math.min(eski.length, yeni.length);
  let icerikYaz = 0;
  for (let i = 0; i < ortak; i++) if (eski[i] !== yeni[i]) icerikYaz++;
  return { olustur: Math.max(0, yeni.length - eski.length),
    kaldir: Math.max(0, eski.length - yeni.length), tasi: 0, icerikYaz };
}

// Eski siralamasi artan kalan en uzun alt dizi: bu dugumler yerinde kalabilir.
function enUzunArtanAltDizi(dizi) {
  const kuyruk = [];
  for (const deger of dizi) {
    let alt = 0, ust = kuyruk.length;
    while (alt < ust) {
      const orta = (alt + ust) >> 1;
      if (kuyruk[orta] < deger) alt = orta + 1; else ust = orta;
    }
    kuyruk[alt] = deger;
  }
  return kuyruk.length;
}

// B) Anahtara gore esleme: dugum, kaydin anahtariyla bulunur.
function anahtarlaEsle(eski, yeni) {
  const eskiSira = new Map(eski.map((a, i) => [a, i]));
  const kalanlar = yeni.filter((a) => eskiSira.has(a)).map((a) => eskiSira.get(a));
  return {
    olustur: yeni.filter((a) => !eskiSira.has(a)).length,
    kaldir: eski.filter((a) => !yeni.includes(a)).length,
    tasi: kalanlar.length - enUzunArtanAltDizi(kalanlar),
    icerikYaz: 0,
  };
}

const satir = (etiket, s) => `${etiket.padEnd(16)} olustur ${s.olustur}  kaldir ` +
  `${s.kaldir}  tasi ${s.tasi}  icerikYaz ${s.icerikYaz}  → toplam ` +
  `${s.olustur + s.kaldir + s.tasi + s.icerikYaz}`;

console.log(`eski liste: ${ESKI.join(", ")}\n`);
for (const [ad, yeni] of Object.entries(DURUMLAR)) {
  console.log(`${ad}: ${yeni.join(", ")}`);
  console.log(`  ${satir("siraya gore", siraylaEsle(ESKI, yeni))}`);
  console.log(`  ${satir("anahtara gore", anahtarlaEsle(ESKI, yeni))}`);
}
```

```
eski liste: sicaklik, nem, ruzgar, kar

sona ekleme: sicaklik, nem, ruzgar, kar, basinc
  siraya gore      olustur 1  kaldir 0  tasi 0  icerikYaz 0  → toplam 1
  anahtara gore    olustur 1  kaldir 0  tasi 0  icerikYaz 0  → toplam 1
basa ekleme: basinc, sicaklik, nem, ruzgar, kar
  siraya gore      olustur 1  kaldir 0  tasi 0  icerikYaz 4  → toplam 5
  anahtara gore    olustur 1  kaldir 0  tasi 0  icerikYaz 0  → toplam 1
ortadan silme: sicaklik, nem, kar
  siraya gore      olustur 0  kaldir 1  tasi 0  icerikYaz 1  → toplam 2
  anahtara gore    olustur 0  kaldir 1  tasi 0  icerikYaz 0  → toplam 1
yeniden sirala: kar, sicaklik, ruzgar, nem
  siraya gore      olustur 0  kaldir 0  tasi 0  icerikYaz 3  → toplam 3
  anahtara gore    olustur 0  kaldir 0  tasi 2  icerikYaz 0  → toplam 2
```

Sona eklemede iki kural aynı sonucu verir: eski düğümlerin hiçbiri kaymamıştır.

Başa eklemede fark açılır. Sıraya göre eşleme dört düğümün de içeriğini yeniden yazar,
çünkü her düğüm bir sonraki kayıtla eşleşmiştir; anahtara göre eşleme tek bir düğüm üretir
ve hiçbirine dokunmaz. Fark listenin uzunluğuyla büyür: yüz kayıtlık bir listenin başına
bir satır eklemek, sıraya göre eşlemede yüz içerik yazımı üretir.

Ortadan silmede sıraya göre eşleme son düğümü kaldırır ve kalan düğümlerden birini yeniden
yazar; anahtara göre eşleme doğru düğümü kaldırır. Görünen sonuç aynıdır, ama kaldırılan
düğüm farklıdır — bu ayrımın önemi bir sonraki bölümdedir.

Yeniden sıralamada anahtara göre eşleme taşımaya döner. Taşınacak en az düğüm sayısı,
eski sıralaması artan kalan en uzun alt dizinin dışında kalan düğümlerin sayısıdır; dört
düğümlük listede bu sayı ikidir.

#### Anahtarın Seçimi

Anahtarın üç koşulu vardır.

**Kardeşleri arasında benzersiz.** Anahtar bütün sayfada değil, yalnızca aynı listedeki
kardeşler arasında ayırt edici olmalıdır. Yinelenen anahtar, iki kaydın aynı düğümü
istemesi demektir.

**Kararlı.** Aynı kayıt, iki oluşturma arasında aynı anahtarı taşımalıdır. Oluşturma
sırasında üretilen rastgele bir değer ya da sıra numarası bu koşulu bozar: her oluşturmada
bütün anahtarlar değişir, bütün düğümler yeniden kurulur ve anahtar kullanmanın kazancı
tersine döner.

**Veriden gelen.** Anahtar kaydın kendi kimliğidir; kaydı ayırt eden bir alan yoksa, kayıt
üretilirken bir kimlik verilir. Görünen bir alanın — ölçüm adının — anahtar yapılması,
o alan düzenlenebilir olduğunda kararlılığı bozar.

Dizinin anahtar olarak kullanılması bu koşulların ikincisini yalnızca tek bir durumda
sağlar: listenin sırası hiç değişmiyor, araya ekleme ve ortadan silme olmuyorsa. Bu
koşulun bugün sağlandığı bir listede yarın bir sıralama düğmesi belirdiğinde, hata
listeleme kodunda değil sıralama düğmesinde görünür.

#### Kimliğin Korunması

Eşlemenin asıl konusu işlem sayısı değildir. Düğümün kimliği, düğüme bağlı olan ve
görünüm tanımında bulunmayan her şeyin adresidir.

```js
// kimlik-korunumu.mjs — anahtarin yerel durumu hangi kayda bagladigi
const KAYITLAR = [
  { kimlik: "s1", ad: "Sıcaklık" },
  { kimlik: "n1", ad: "Bağıl nem" },
  { kimlik: "r1", ad: "Rüzgâr" },
];

// Her satirin bir onay kutusu var; secim, satirin ornek kaydinda yasiyor.
function ciz(ornekler, kayitlar, anahtarla) {
  return kayitlar.map((kayit, dizin) => {
    const anahtar = String(anahtarla(kayit, dizin));
    if (!ornekler.has(anahtar)) ornekler.set(anahtar, { secili: false });
    return { anahtar, ad: kayit.ad, ornek: ornekler.get(anahtar) };
  });
}

const ANAHTARLAR = {
  "dizin": (kayit, dizin) => dizin,
  "kaydin kimligi": (kayit) => kayit.kimlik,
};

for (const [ad, anahtarla] of Object.entries(ANAHTARLAR)) {
  const ornekler = new Map();
  let satirlar = ciz(ornekler, KAYITLAR, anahtarla);
  satirlar.find((s) => s.ad === "Rüzgâr").ornek.secili = true;  // kullanici isaretledi

  // Listenin basina yeni bir olcum giriyor; ayni bilesenler yeniden ciziliyor.
  satirlar = ciz(ornekler, [{ kimlik: "b1", ad: "Basınç" }, ...KAYITLAR], anahtarla);
  const isaretli = satirlar.filter((s) => s.ornek.secili).map((s) => s.ad);
  console.log(`anahtar = ${ad.padEnd(15)} → isaretli satir: ${isaretli.join(", ") || "yok"}`);
}

// Kosullu olusturmada da ayni kural gecerlidir: ayni konumdaki ayni tur, ayni ornek.
function kosulluCiz(ornekler, kip, anahtarla) {
  const tanim = kip === "arama"
    ? { tur: "input", rol: "arama" }
    : { tur: "input", rol: "not" };
  const anahtar = anahtarla(tanim, kip);
  if (!ornekler.has(anahtar)) ornekler.set(anahtar, { yazilan: "" });
  return { anahtar, rol: tanim.rol, ornek: ornekler.get(anahtar) };
}

for (const [ad, anahtarla] of Object.entries({
  "yalniz tur": (tanim) => tanim.tur,
  "tur + dal": (tanim, kip) => `${tanim.tur}:${kip}`,
})) {
  const ornekler = new Map();
  kosulluCiz(ornekler, "arama", anahtarla).ornek.yazilan = "kar";  // kullanici yazdi
  const not = kosulluCiz(ornekler, "not", anahtarla);
  console.log(`\nkosul anahtari = ${ad.padEnd(11)} → dal: ${not.rol}, ` +
    `alandaki metin: ${JSON.stringify(not.ornek.yazilan)}, ` +
    `ornek sayisi: ${ornekler.size}`);
}
```

```
anahtar = dizin           → isaretli satir: Bağıl nem
anahtar = kaydin kimligi  → isaretli satir: Rüzgâr

kosul anahtari = yalniz tur  → dal: not, alandaki metin: "kar", ornek sayisi: 1

kosul anahtari = tur + dal   → dal: not, alandaki metin: "", ornek sayisi: 2
```

Birinci bölümde kullanıcı rüzgâr satırını işaretlemiştir. Listenin başına bir ölçüm
girdiğinde dizin anahtarı işareti bir satır kaydırır ve işaret bağıl nem satırında görünür.
Kaydın kimliği anahtar olduğunda işaret kaydıyla birlikte kalır.

Bu, veri hatası değildir: durumun kendisi doğrudur, yanlış olan durumun hangi kayda ait
sayıldığıdır. Aynı kayma odaklı alanda, kaydırma konumunda, açılmış ayrıntı bölümünde ve
süren geçişte de olur. Kullanıcı için görünen sonuç, işaretlemediği bir satırın işaretli
görünmesidir.

İkinci bölüm aynı kuralın koşullu oluşturmadaki karşılığıdır. İki dal aynı konumda aynı tür
ögeyi ürettiğinde eşleşme tür üzerinden kurulur, örnek yeniden kullanılır ve arama
alanına yazılan metin not alanında görünmeye devam eder. Dalları ayıran bir anahtar
verildiğinde iki ayrı örnek oluşur ve metin taşınmaz.

Buradan iki yönlü bir kural çıkar. Aynı kalması gereken şeyler için anahtar **aynı**
tutulur; ayrılması gereken şeyler için anahtar **bilerek değiştirilir**. İkinci yön, bir
bileşenin durumunu sıfırlamanın yoludur: bileşene yeni bir anahtar verilir, eski örnek
sökülür, yeni örnek baştan kurulur.

#### Özet

- Koşullu oluşturmada ögeyi üretmemek ile gizlemek eş değildir: üretilmeyen öge ağaçta,
  erişilebilirlik ağacında ve sekme sırasında yoktur; gizlenen öge bellekte kalır. Sık
  açılıp kapanan pahalı parçalar üretilip gizlenir, nadir görünen büyük parçalar üretilmez.
- Sıraya göre eşleme listenin başına ekleme yapıldığında bütün düğümlerin içeriğini yeniden
  yazar; anahtara göre eşleme tek bir düğüm üretir.
- Yeniden sıralamada taşınacak en az düğüm sayısı, eski sıralaması artan kalan en uzun alt
  dizinin dışındaki düğümlerdir.
- Anahtar kardeşleri arasında benzersiz, oluşturmalar arasında kararlı ve veriden gelen bir
  değer olmalıdır; dizin yalnızca sırası hiç değişmeyen listelerde bu koşulu sağlar.
- Kimlik korunmadığında görünüm tanımında bulunmayan durum yanlış kayda bağlanır: onay
  kutusu, odak, kaydırma konumu, açık ayrıntı ve süren geçiş.
- Anahtarı bilerek değiştirmek, bir bileşenin durumunu sıfırlamanın yoludur.

#### Sonraki Adım

Bu ders eşleme kuralını liste düzeyinde kurdu, ama listedeki bir düğümün eşleştikten sonra
ne olacağını açık bıraktı: öznitelikleri nasıl karşılaştırılır, çocukları hangi sırayla
gezilir, türü değiştiğinde ne yapılır. Sonraki ders bu kuralların tamamını tek bir
algoritmada toplar, sanal ağaç karşılaştırmasının hangi varsayımlarla ucuzladığını gösterir
ve aynı işi karşılaştırma yapmadan çözen yaklaşımları bunun yanına koyar.

### Uzlaşma

Source: https://academia.sh/tr/kurslar/bilesen-tabanli-gelistirme/uzlasma

Yeni görünüm tanımı ile öncekinin karşılaştırılması; sanal ağaç, karşılaştırmayı doğrusala indiren üç varsayım, yama listesinin üretilmesi ve karşılaştırma yapmadan güncelleyen yaklaşımlarla ölçülmüş kıyas.

Önceki ders eşleme kuralını liste düzeyinde kurdu: hangi kaydın hangi düğüme karşılık
geldiğini anahtar belirler. Eşleştikten sonrası açık kaldı — öznitelikleri nasıl
karşılaştırılır, çocukları hangi sırayla gezilir, türü değiştiğinde ne olur.

Bu ders bu kuralların tamamını tek bir algoritmada toplar. Algoritmanın adı **uzlaşma
(reconciliation)** dır: girdisi iki görünüm tanımı, çıktısı ağaca uygulanacak işlemlerin
listesidir.

#### Uzlaşmanın Girdisi ve Çıktısı

Çerçeve, en son ürettiği görünüm tanımını bellekte tutar. Bu saklanan tanıma **sanal ağaç
(virtual tree)** denir: belge ağacının o anki hâlinin sade veriden yapılmış bir kopyası.

Durum değiştiğinde yeni bir tanım üretilir ve iki tanım karşılaştırılır. Karşılaştırmanın
sonucu bir **yama listesi**dir — düğüm oluştur, kaldır, taşı, öznitelik yaz, metin yaz.
Belge ağacına yalnızca bu liste uygulanır.

Sanal ağacın varlık nedeni buradadır. Karşılaştırma belge ağacı üzerinde yapılsaydı, her
adımda ağaçtan okuma yapılırdı; ağaçtan okumak yerleşim hesabını tetikleyebilir ve
Başarım Kaydı dersindeki yerleşim sarsıntısını üretir. Sade veriden yapılmış bir kopya
üzerinde karşılaştırma yapmak bu okumaları ortadan kaldırır.

#### Karşılaştırmayı Ucuzlatan Üç Varsayım

İki sıralı ağaç arasındaki en küçük dönüşüm dizisini bulmak, ağaç boyutuna göre kübik
mertebede bir iştir. Her karede yapılamayacak kadar pahalıdır. Uzlaşma bu problemi üç
varsayımla doğrusala indirir.

**Türü değişen düğümün alt ağacı karşılaştırılmaz.** Bir konumda liste ögesi yerine bölüm
ögesi belirmişse, eski alt ağaç bütünüyle atılır ve yenisi baştan kurulur. Varsayımın
gerekçesi, farklı türde iki ögenin farklı yapılar üretmesinin beklenmesidir. Bedeli, aynı
içeriği taşıyan iki farklı sarmalayıcı arasında geçiş yapmanın bütün alt ağacı — ve
içindeki durumu — yeniden kurmasıdır.

**Karşılaştırma düzey düzeyde yapılır.** Bir düğümün başka bir düzeye taşınmış olabileceği
hiç denenmez. Bir satırın tablodan çıkıp bir açılır panele girmesi, taşıma değil kaldırma
ve oluşturma olarak görülür.

**Kardeşler anahtarla eşleşir.** Önceki dersin kuralı, algoritmanın çocuk gezinme adımıdır.
Anahtarı olmayan kardeşler konumlarına göre eşleşir.

#### Algoritma

Aşağıdaki gerçekleştirim bu üç varsayımı uygular ve yama listesini üretir.

```js
// uzlasma.mjs — iki gorunum tanimini karsilastirip yama listesi uretir
const oge = (ad, ozn = {}, cocuklar = [], anahtar = null) =>
  ({ ad, ozn, cocuklar, anahtar });

function gorunum(durum) {
  return oge("section", { class: "pano" }, [
    oge("h2", {}, [`Kuzey Yamaç — ${durum.olcumler.length} ölçüm`]),
    oge("ul", { class: "tablo" }, durum.olcumler.map((o) =>
      oge("li", { class: o.deger >= o.esik ? "rozet asim" : "rozet" },
        [`${o.ad}: ${o.deger} ${o.birim}`], o.kimlik))),
  ]);
}

// Eski siralamasi artan kalan en uzun alt dizi: yerinde kalabilecek dugumler.
function enUzunArtanAltDizi(dizi) {
  const kuyruk = [], geri = [];
  for (const [k, deger] of dizi.entries()) {
    let alt = 0, ust = kuyruk.length;
    while (alt < ust) {
      const orta = (alt + ust) >> 1;
      if (dizi[kuyruk[orta]] < deger) alt = orta + 1; else ust = orta;
    }
    geri[k] = alt > 0 ? kuyruk[alt - 1] : -1;
    kuyruk[alt] = k;
  }
  const sonuc = new Set();
  for (let k = kuyruk.at(-1); k !== undefined && k !== -1; k = geri[k]) sonuc.add(k);
  return sonuc;
}

function uzlas(eski, yeni, yol, yamalar) {
  if (typeof eski === "string" || typeof yeni === "string") {
    if (eski !== yeni) yamalar.push(`METIN   ${yol}  ${JSON.stringify(yeni)}`);
    return;
  }
  // 1. Tur ya da anahtar farkliysa alt agac karsilastirilmaz, yenisiyle degistirilir.
  if (eski.ad !== yeni.ad || eski.anahtar !== yeni.anahtar) {
    yamalar.push(`DEGISTIR ${yol}  <${eski.ad}> → <${yeni.ad}>`);
    return;
  }
  // 2. Oznitelikler iki yonlu karsilastirilir.
  for (const [k, v] of Object.entries(yeni.ozn))
    if (eski.ozn[k] !== v) yamalar.push(`OZNITELIK ${yol}  ${k}=${JSON.stringify(v)}`);
  for (const k of Object.keys(eski.ozn))
    if (!(k in yeni.ozn)) yamalar.push(`SIL-OZN ${yol}  ${k}`);
  // 3. Cocuklar: anahtarli olanlar anahtarla, anahtarsizlar konumla eslesir.
  const eskiSira = new Map();
  eski.cocuklar.forEach((c, i) =>
    eskiSira.set(typeof c === "string" || c.anahtar === null ? `#${i}` : c.anahtar, i));
  const eslesenler = [];
  yeni.cocuklar.forEach((c, i) => {
    const anahtar = typeof c === "string" || c.anahtar === null ? `#${i}` : c.anahtar;
    const eskiDizin = eskiSira.get(anahtar);
    if (eskiDizin === undefined) yamalar.push(`OLUSTUR ${yol}/${i}  ${anahtar}`);
    else eslesenler.push({ i, eskiDizin, anahtar });
  });
  for (const [anahtar, i] of eskiSira)
    if (!eslesenler.some((e) => e.anahtar === anahtar))
      yamalar.push(`KALDIR  ${yol}/${i}  ${anahtar}`);
  const kalanlar = enUzunArtanAltDizi(eslesenler.map((e) => e.eskiDizin));
  eslesenler.forEach((e, k) => {
    if (!kalanlar.has(k)) yamalar.push(`TASI    ${yol}/${e.eskiDizin} → ${yol}/${e.i}  ${e.anahtar}`);
    uzlas(eski.cocuklar[e.eskiDizin], yeni.cocuklar[e.i], `${yol}/${e.i}`, yamalar);
  });
}

const O = (kimlik, ad, deger, birim, esik) => ({ kimlik, ad, deger, birim, esik });
const ONCE = { olcumler: [
  O("s1", "Sıcaklık", -4.2, "°C", 30), O("n1", "Bağıl nem", 72, "%", 90),
  O("r1", "Rüzgâr", 11.4, "m/s", 25), O("k1", "Kar derinliği", 38, "cm", 60),
] };
const SONRA = { olcumler: [
  O("k1", "Kar derinliği", 38, "cm", 60), O("s1", "Sıcaklık", -4.2, "°C", 30),
  O("n1", "Bağıl nem", 94, "%", 90), O("b1", "Basınç", 964, "hPa", 1050),
] };

const yamalar = [];
uzlas(gorunum(ONCE), gorunum(SONRA), "kok", yamalar);
const sayilan = (tanim) => typeof tanim === "string"
  ? 1 : 1 + tanim.cocuklar.reduce((t, c) => t + sayilan(c), 0);
console.log(`tanimdaki dugum sayisi: ${sayilan(gorunum(SONRA))}`);
console.log(`uretilen yama sayisi  : ${yamalar.length}\n`);
console.log(yamalar.join("\n"));
```

```
tanimdaki dugum sayisi: 12
uretilen yama sayisi  : 5

OLUSTUR kok/1/3  b1
KALDIR  kok/1/2  r1
TASI    kok/1/3 → kok/1/0  k1
OZNITELIK kok/1/2  class="rozet asim"
METIN   kok/1/2/0  "Bağıl nem: 94 %"
```

Aynı anda dört şey değişmiştir: bir ölçüm eklenmiş, biri çıkarılmış, liste yeniden
sıralanmış ve bir ölçümün değeri eşiği aşmıştır. On iki düğümlük tanım için beş yama
üretilmiştir.

**Tek taşıma yeterlidir.** Dört satırın sırası değişmiş olsa da, eski sıralaması artan
kalan en uzun alt dizide bulunan satırlar yerinde bırakılmıştır ve yalnızca kar derinliği
taşınmıştır.

**Eşik aşımı iki ayrı yama üretmiştir.** Bağıl nemin sınıfı ve metni ayrı alanlardır; her
alan kendi karşılaştırmasından geçer. Bu ayrım, öznitelik yazmanın metin yazmaktan farklı
bir maliyeti olması nedeniyle korunur.

**Başlık hiç yama üretmemiştir.** Ölçüm sayısı dörtte kaldığı için başlığın metni
değişmemiştir; karşılaştırma bunu görmüş ve hiçbir işlem yazmamıştır. Bir alanın
karşılaştırılması ile yazılması arasındaki fark budur: karşılaştırma ucuz, yazma pahalıdır.

**Değişen ölçümün kendisi taşınmamıştır.** Kaldırma, oluşturma ve taşıma anahtarla
çözüldüğü için, güncellenen satır listede yer değiştirmiş olmasına rağmen kendi düğümünde
kalmıştır. Anahtar olmasaydı bu satır da yeniden yazılırdı.

#### Maliyet Nerede

Uzlaşma, üretilen tanımın büyüklüğüne göre doğrusaldır. Buradan pratik bir sonuç çıkar:
maliyeti düşürmenin yolu karşılaştırmayı hızlandırmak değil, **daha küçük bir tanım
üretmektir**.

Tanımın büyüklüğünü belirleyen şey, üçüncü derste ölçülen yeniden çizim alanıdır. Kökteki
bir durum değiştiğinde bütün ağaç yeniden çizilir ve tanımın tamamı üretilip
karşılaştırılır. Durumu olabildiğince aşağıda tutmak, girdisi değişmemiş alt ağaçların
üretimini atlamak ve pahalı hesapların sonucunu saklamak bu alanı daraltan araçlardır;
son ikisi kursun ilerleyen konularında ele alınır.

#### Karşılaştırma Yapmayan Yaklaşımlar

Uzlaşma, "hangi değerin hangi düğümü etkilediği" sorusunu her seferinde yeniden çözer.
Aynı soruyu bir kez çözüp saklamak da olanaklıdır ve çerçeve ailelerini ayıran temel tasarım
farkı budur.

```js
// yaklasimlar.mjs — tek bir olcum degeri degistiginde uc yaklasimin yaptigi is
const N = 12;
const OLCUMLER = Array.from({ length: N }, (_, i) => ({
  kimlik: `s${i}`, ad: `Sensör ${String(i + 1).padStart(2, "0")}`, deger: 10 + i,
}));
const DEGISEN = 6, YENI_DEGER = 17.5;
const satirMetni = (o) => `${o.ad}: ${o.deger}`;

// --- A) Sanal agac: tanimi yeniden uret, eskisiyle karsilastir ---------------
let uretim = 0, karsilastirma = 0, yazmaA = 0;
const oge = (ad, cocuklar) => { uretim++; return { ad, cocuklar }; };
const metin = (s) => { uretim++; return s; };
const gorunum = (olcumler) =>
  oge("ul", olcumler.map((o) => oge("li", [metin(satirMetni(o))])));

function uzlas(eski, yeni, agac) {
  karsilastirma++;
  if (typeof yeni === "string") {
    if (eski !== yeni) { agac.metinler[agac.k] = yeni; yazmaA++; }
    agac.k++;
    return;
  }
  eski.cocuklar.forEach((c, i) => uzlas(c, yeni.cocuklar[i], agac));
}

const agacA = { metinler: OLCUMLER.map(satirMetni), k: 0 };
const eskiTanim = gorunum(OLCUMLER);
uretim = 0;                                    // ilk cizim sayilmaz
const yeniOlcumler = OLCUMLER.map((o, i) =>
  i === DEGISEN ? { ...o, deger: YENI_DEGER } : o);
uzlas(eskiTanim, gorunum(yeniOlcumler), agacA);

// --- B) Tepkisel bagimlilik takibi: baglama okudugu hucreye abone olur -------
let kayitB = 0, yazmaB = 0;
const agacB = { metinler: OLCUMLER.map(satirMetni) };
const hucreler = OLCUMLER.map((o, i) => {
  const hucre = { deger: o.deger, aboneler: [] };
  hucre.aboneler.push((v) => { agacB.metinler[i] = `${o.ad}: ${v}`; yazmaB++; });
  kayitB++;                                     // kurulumda bir abonelik kaydi
  return hucre;
});
const hucreYaz = (i, v) => {
  hucreler[i].deger = v;
  for (const abone of hucreler[i].aboneler) abone(v);
};
hucreYaz(DEGISEN, YENI_DEGER);

// --- C) Derleme zamaninda tepkisellestirme: eslesme derleyiciden gelir -------
// Derleyici sablonu okur: satir metni yalnizca "deger" alanina baglidir.
let yazmaC = 0;
const agacC = { metinler: OLCUMLER.map(satirMetni) };
const DERLENMIS = {
  deger: (i, v) => { agacC.metinler[i] = `${OLCUMLER[i].ad}: ${v}`; yazmaC++; },
};
DERLENMIS.deger(DEGISEN, YENI_DEGER);

// --- Sonuc ------------------------------------------------------------------
const beklenen = yeniOlcumler.map(satirMetni);
const ayni = (m) => JSON.stringify(m) === JSON.stringify(beklenen);
console.log(`${N} satirlik tabloda tek bir olcum degeri degisti\n`);
console.log("yaklasim".padEnd(26) + "kurulum  uretim  karsilastirma  yazma  sonuc");
console.log(`${"sanal agac".padEnd(26)}${String(0).padEnd(9)}${String(uretim).padEnd(8)}` +
  `${String(karsilastirma).padEnd(15)}${String(yazmaA).padEnd(7)}${ayni(agacA.metinler)}`);
console.log(`${"tepkisel bagimlilik".padEnd(26)}${String(kayitB).padEnd(9)}${String(0).padEnd(8)}` +
  `${String(0).padEnd(15)}${String(yazmaB).padEnd(7)}${ayni(agacB.metinler)}`);
console.log(`${"derleme zamani".padEnd(26)}${String(0).padEnd(9)}${String(0).padEnd(8)}` +
  `${String(0).padEnd(15)}${String(yazmaC).padEnd(7)}${ayni(agacC.metinler)}`);
```

```
12 satirlik tabloda tek bir olcum degeri degisti

yaklasim                  kurulum  uretim  karsilastirma  yazma  sonuc
sanal agac                0        25      25             1      true
tepkisel bagimlilik       12       0       0              1      true
derleme zamani            0        0       0              1      true
```

Üç yaklaşım aynı sonucu üretir; yaptıkları iş farklıdır.

**Sanal ağaç tutan yaklaşım** güncelleme başına tanımın tamamını üretir ve gezer: yirmi beş
düğüm üretimi, yirmi beş karşılaştırma, bir yazma. Karşılığında hiçbir şey kaydetmez ve
görünüm işlevine hiçbir kısıt koymaz — koşul, döngü, yardımcı işlev, hepsi sıradan koddur.

**Tepkisel bağımlılık takibi yapan yaklaşım**, kurulumda hangi bağlamanın hangi değeri
okuduğunu kaydeder. Güncellemede tanım üretmez ve karşılaştırma yapmaz; doğrudan ilgili
bağlamayı çalıştırır. Bedeli, her bağlama için tutulan kayıt ve okumaların izlenebilir
olması zorunluluğudur: değerlere doğrudan değil, izlenen bir sarmalayıcı üzerinden
erişilir.

**Derleme zamanında tepkiselleştiren yaklaşım** aynı eşlemeyi çalışma zamanında değil,
derleyicide kurar. Şablonun hangi parçasının hangi alanı okuduğu derlemede bellidir;
çıktı, doğrudan o alanı yazan koddur. Çalışma zamanında ne kayıt tutulur ne karşılaştırma
yapılır. Bedeli, bir derleme adımının zorunlu olması ve şablon dilinin çözümlenebilir
kalacak kadar kısıtlı tutulmasıdır: derleyicinin göremediği devingen yapılar bu
eşlemenin dışında kalır.

Tabloda görünmeyen bir ödünleşim daha vardır. Sanal ağaç yaklaşımında güncelleme maliyeti
**yeniden çizilen alanla**, diğer ikisinde **değişen değer sayısıyla** orantılıdır. Küçük
bir arayüzde fark ölçülemez; on beş rozetin saniyede birkaç kez güncellendiği bir panoda
belirginleşir. Seçimi yalnızca bu sayı belirlemez — kursun son konusu ölçütlerin tamamını
ele alır.

#### Özet

- Uzlaşma, yeni görünüm tanımı ile saklanan öncekini karşılaştırıp yama listesi üretir;
  saklanan tanıma sanal ağaç denir ve belge ağacından okuma yapılmasını önler.
- İki sıralı ağaç arasındaki en küçük dönüşümü bulmak kübik mertebededir; üç varsayım —
  tür değişiminde alt ağacı atmak, düzey düzey karşılaştırmak, kardeşleri anahtarla
  eşlemek — problemi doğrusala indirir.
- Varsayımların bedeli vardır: sarmalayıcı türünü değiştirmek alt ağacı ve durumunu yeniden
  kurar, düzey değiştiren bir düğüm taşınmaz, kaldırılıp yeniden oluşturulur.
- Karşılaştırma ucuz, yazma pahalıdır; değişmeyen alanlar için hiçbir yama üretilmez.
- Uzlaşmanın maliyeti üretilen tanımın büyüklüğüne bağlıdır; düşürmenin yolu daha küçük bir
  tanım üretmek, yani yeniden çizim alanını daraltmaktır.
- Aynı işi tepkisel bağımlılık takibi kurulumda tutulan kayıtla, derleme zamanı yaklaşımı
  derleyicideki eşlemeyle çözer; ilki izlenebilir erişim, ikincisi kısıtlı bir şablon dili
  ve derleme adımı ister.

#### Sonraki Adım

Bu konu, arayüzün durumdan nasıl üretildiğini ve üretilenin ağaca nasıl yansıdığını kurdu:
görünüm bir işlevdir, bileşen bu işlevin sözleşmeli bir parçasıdır, şablon işaretlemeyi
veriye bağlar, anahtar kimliği korur, uzlaşma farkı yamaya çevirir. Geriye çözülmeyen bir
şey kaldı: durumun kendisi. Uzlaşma her zaman bir yeniden çizimden sonra çalışır — peki
yeniden çizimi ne başlatır? Bir olay işleyicisi iki değeri arka arkaya güncellediğinde iki
kez mi çizilir, yoksa güncellemeler toplu mu işlenir? Bir bileşenin yerel durumu tam
olarak nerede tutulur ve o değeri güncelleyen çağrı, hemen sonraki satırda okunduğunda
neyi döndürür? Sonraki konu, Bileşen Yaşam Döngüsü ve Durum, Yerel Durum dersiyle bu
sorularla başlar.

## Bileşen Yaşam Döngüsü ve Durum

Bileşenin oluşturmalar arası yaşayan değerleri; güncellemenin toplu işlenmesi, yan etkilerin kaydı ve temizlenmesi, türetilmiş değerlerin bellenmesi, oluşturma dışı başvurular, bağlamla değer sağlama, hata sınırları, askıya alma ve ağaç dışına taşıma.

### Yerel Durum

Source: https://academia.sh/tr/kurslar/bilesen-tabanli-gelistirme/yerel-durum

Bileşenin oluşturmalar arası yaşayan değeri; durum yuvalarının çağrı sırasına bağlanması, güncellemenin kuyruğa alınması, tek turdaki güncellemelerin toplu işlenmesi, eskimiş değer tuzağı ve durumun ağaçta nereye konacağı.

Önceki ders ağacın nasıl güncellendiğini çözdü: iki oluşturmanın ürettiği ağaçlar
karşılaştırılıyor ve aradaki fark en küçük dokunuşlarla belgeye uygulanıyor. Uzlaşma bittiğinde
geriye tek bir soru kalır. Karşılaştırma her zaman bir yeniden çizimden sonra yapılır — peki
yeniden çizimi ne başlatır?

Yanıt, bileşenin kendi içinde tuttuğu değerdir. Kuzey Yamaç Ölçüm İstasyonu sayfasındaki
süzgeç paneli hangi ölçüm türünün seçili olduğunu bilir, ölçüm tablosu hangi sütuna göre
sıralandığını bilir, her ölçüm rozeti açılıp açılmadığını bilir. Bu değerler sunucudan
gelmez, üst bileşenden verilmez ve sayfa yenilenene kadar yaşar. Bu derste bunlara
**yerel durum (local state)** denir; dersin konusu durumun nerede saklandığı, bir
güncellemenin ne zaman görünür olduğu ve bir turdaki üç güncellemenin kaç oluşturma
ürettiğidir.

#### Durum Nerede Yaşar

Bir bileşenin gövdesi her oluşturmada baştan çalışan bir işlevdir. İşlev gövdesinde
tanımlanan yerel değişkenler her çağrıda sıfırlanır; ikinci oluşturmada birinci
oluşturmadan kalan hiçbir şey yoktur. Durumun bu gövdenin dışında bir yerde yaşaması
gerekir.

Yaşadığı yer, bileşenin **örneğidir**. Ağaçtaki her bileşen konumuna karşılık bir örnek
kaydı tutulur ve bu kayıtta bir yuva listesi bulunur. Gövde çalışırken çalışma zamanı bir
imleç tutar: gövde her durum isteğinde imleci bir ilerletir ve o sıradaki yuvayı verir.
İlk oluşturmada yuva yoksa başlangıç değeriyle açılır, sonraki oluşturmalarda var olan
yuva okunur.

Gövdenin bu yuvaya erişmek için çağırdığı işleve **durum kancası (state hook)** denir.
Kanca iki şey döndürür: yuvadaki güncel değer ve o yuvaya yazma isteği gönderen bir
işlev. Değer okunur, yazma isteği çağrılır; ikisinin arasında doğrudan bir atama yoktur.

#### Güncelleme Bir İstek, Atama Değildir

Yazma işlevinin adı yanıltıcı olabilir. Çağrıldığı anda yuvaya yazmaz; bir güncelleme
isteğini kuyruğa koyar ve o bileşeni kirli olarak işaretler. Kuyruk, çalışan kod bittikten
sonra boşaltılır. Aşağıdaki model bu kuyruğu, kuyruğun boşaltılmasını ve boşaltmanın kaç
oluşturma ürettiğini görünür kılar.

```js
// durum-kuyrugu.mjs — yerel durum, guncelleme kuyrugu ve toplu isleme
let etkinOrnek = null;
let cizimSayisi = 0;
const kuyruk = [];

function durumKancasi(ilk) {
  const o = etkinOrnek;
  const i = o.imlec++;
  if (o.yuvalar.length === i) o.yuvalar.push(typeof ilk === "function" ? ilk() : ilk);
  return [o.yuvalar[i], (yeni) => { kuyruk.push({ o, i, yeni }); }];
}

function olustur(ornek) {
  etkinOrnek = ornek;
  ornek.imlec = 0;
  const cikti = ornek.govde();
  etkinOrnek = null;
  cizimSayisi++;
  console.log(`  cizim ${cizimSayisi}: ${cikti}`);
}

// Olay isleyicisi bittiginde calisir: kuyruk bosaltilir, her ornek bir kez cizilir.
function turuBitir() {
  const kirliler = [];
  for (const is of kuyruk) {
    const eski = is.o.yuvalar[is.i];
    is.o.yuvalar[is.i] = typeof is.yeni === "function" ? is.yeni(eski) : is.yeni;
    if (!kirliler.includes(is.o)) kirliler.push(is.o);
  }
  console.log(`  [tur sonu] kuyrukta ${kuyruk.length} guncelleme, ${kirliler.length} ornek kirli`);
  kuyruk.length = 0;
  for (const o of kirliler) olustur(o);
}

const rozet = { ad: "rozet", yuvalar: [], imlec: 0, dis: null };
rozet.govde = () => {
  const [deger, yazDeger] = durumKancasi(0);
  const [birim, yazBirim] = durumKancasi("C");
  rozet.dis = { deger, yazDeger, yazBirim };
  return `deger=${deger} birim=${birim}`;
};

console.log("A. dogrudan deger yazan uc guncelleme, tek olay icinde");
olustur(rozet);
rozet.dis.yazDeger(rozet.dis.deger + 1);
rozet.dis.yazDeger(rozet.dis.deger + 1);
rozet.dis.yazBirim("F");
console.log(`  yazmadan hemen sonra govdedeki deger: ${rozet.dis.deger}`);
turuBitir();

console.log("B. ayni islem, guncelleyici islevle");
rozet.yuvalar = [0, "C"];
cizimSayisi = 0;
olustur(rozet);
rozet.dis.yazDeger((onceki) => onceki + 1);
rozet.dis.yazDeger((onceki) => onceki + 1);
rozet.dis.yazBirim("F");
turuBitir();

console.log("C. iki ayri turda yapilan ayni iki guncelleme");
rozet.yuvalar = [0, "C"];
cizimSayisi = 0;
olustur(rozet);
rozet.dis.yazDeger((onceki) => onceki + 1);
turuBitir();
rozet.dis.yazDeger((onceki) => onceki + 1);
turuBitir();
```

```
A. dogrudan deger yazan uc guncelleme, tek olay icinde
  cizim 1: deger=0 birim=C
  yazmadan hemen sonra govdedeki deger: 0
  [tur sonu] kuyrukta 3 guncelleme, 1 ornek kirli
  cizim 2: deger=1 birim=F
B. ayni islem, guncelleyici islevle
  cizim 1: deger=0 birim=C
  [tur sonu] kuyrukta 3 guncelleme, 1 ornek kirli
  cizim 2: deger=2 birim=F
C. iki ayri turda yapilan ayni iki guncelleme
  cizim 1: deger=0 birim=C
  [tur sonu] kuyrukta 1 guncelleme, 1 ornek kirli
  cizim 2: deger=1 birim=C
  [tur sonu] kuyrukta 1 guncelleme, 1 ornek kirli
  cizim 3: deger=2 birim=C
```

#### Toplu İşleme

A bölümünün ilk okunacak satırı, yazma çağrılarından hemen sonra gövdedeki değerin hâlâ
sıfır olmasıdır. Gövde, o oluşturmada okuduğu değeri bir kapanışta tutar; kapanış o
oluşturmaya aittir ve yazma isteği onu değiştirmez. Bir oluşturma boyunca durum sabittir.
Bu, bir kısıt değil bir güvencedir: aynı gövde içinde bir değerin bir yerde eski, başka
bir yerde yeni okunması olanaksızdır.

İkinci okunacak satır oluşturma sayısıdır. Üç güncelleme kuyruğa girmiş, tur sonunda
kuyruk boşaltılmış ve **tek** bir oluşturma yapılmıştır. Bir turdaki bütün güncellemelerin
biriktirilip tek oluşturmayla karşılanmasına **toplu işleme (batching)** denir. Toplu
işleme olmasaydı üç yazma üç oluşturma üretirdi; her oluşturma bir uzlaşma turu demek
olduğu için maliyet üçe katlanırdı. Dahası ara oluşturmalar tutarsız görünürdü: değer
güncellenmiş ama birim henüz eski olurdu.

Toplu işlemenin sınırı tur sınırıdır. C bölümünde aynı iki güncelleme iki ayrı turda
yapılmış ve iki oluşturma üretmiştir. Bir olay işleyicisi içinde yapılan güncellemeler
aynı gruba girer; bir zamanlayıcı geri çağırımından ve bir ağ yanıtından gelen
güncellemeler ayrı turlardır ve genellikle ayrı oluşturmalar üretir.

#### Eskimiş Değer ve Güncelleyici İşlev

A ile B arasındaki tek fark yazma çağrısının biçimidir; sonuç birde ve ikide ayrılır.

A'da her iki çağrı da `deger + 1` ifadesini kendi kapanışındaki sıfırdan hesaplamış, ikisi
de bire yazmıştır. İkinci yazma birinciyi ezmez, aynı sonucu yeniden yazar. Bu, **eskimiş
değer (stale value)** tuzağıdır ve tek bir turda aynı yuvaya iki kez yazan her kodda
belirir: sayaç iki artırma sonunda bir artmıştır.

B'de yazma çağrısına değer değil bir işlev verilmiştir. Kuyruk boşaltılırken bu işlev, o
andaki yuva değeriyle çağrılır; birinci istek sıfırdan bire, ikincisi birden ikiye taşır.
Buradan bağlayıcı bir kural çıkar: **yeni değer eski değere bağlıysa güncelleyici işlev
yazılır.** Yeni değer eskisinden bağımsızsa — süzgecin seçilen türü, panelin açık olması —
doğrudan değer yazmak yeterlidir.

#### Çağrı Sırasının Değişmezliği

Modelde yuvalar ada göre değil imlece göre bulunur. Bunun bir bedeli vardır: gövdenin her
oluşturmada durum kancasını **aynı sırayla ve aynı sayıda** çağırması gerekir. Kanca
çağrısı bir koşulun içine alınırsa, koşul değiştiği oluşturmada imleç kayar ve ikinci
yuvanın değeri birinci yuvadan okunur. Hata sessizdir; birbiriyle ilgisiz iki değer yer
değiştirir.

Bu yüzden durum çağrıları gövdenin en üstünde, koşulsuz ve döngüsüz yazılır. Koşullu bir
durum gerekiyorsa koşul kancanın etrafına değil, kancadan sonraki kullanıma konur. Ada
göre çözümleme yapan tasarımlarda bu kısıt yoktur; buna karşılık ad çakışmalarını
denetlemek gerekir. Sıraya dayanan tasarımın kazancı, hiçbir ad kaydı tutmadan sabit
zamanda yuva bulmasıdır.

#### Durumun Ağaçtaki Yeri

Durumun hangi bileşende tutulacağı bir tasarım kararıdır ve doğrudan ölçülebilir bir
maliyeti vardır. Bir yuva değiştiğinde o bileşen ve altındaki bütün ağaç yeniden
oluşturulur; kardeşler ve atalar etkilenmez.

```js
// durumun-yeri.mjs — ayni durumun iki ayri dugumde tutulmasinin olusturma maliyeti
const AGAC = {
  ad: "sayfa",
  cocuklar: [
    { ad: "baslik", cocuklar: [] },
    { ad: "suzgec-paneli", cocuklar: [{ ad: "suzgec-dugmesi", cocuklar: [] },
                                      { ad: "suzgec-listesi", cocuklar: [] }] },
    { ad: "olcum-tablosu", cocuklar: [{ ad: "satir-1", cocuklar: [] },
                                      { ad: "satir-2", cocuklar: [] },
                                      { ad: "satir-3", cocuklar: [] }] },
  ],
};

function bul(dugum, ad) {
  if (dugum.ad === ad) return dugum;
  for (const c of dugum.cocuklar) { const s = bul(c, ad); if (s) return s; }
  return null;
}

// Durumu tutan dugum ve butun alt agaci yeniden olusturulur.
function altAgac(dugum, toplanan = []) {
  toplanan.push(dugum.ad);
  for (const c of dugum.cocuklar) altAgac(c, toplanan);
  return toplanan;
}

const toplam = altAgac(AGAC).length;
for (const sahip of ["sayfa", "suzgec-paneli"]) {
  const cizilen = altAgac(bul(AGAC, sahip));
  console.log(`durum "${sahip}" dugumunde:`);
  console.log(`  yeniden olusturulan ${cizilen.length}/${toplam}: ${cizilen.join(", ")}`);
}
```

```
durum "sayfa" dugumunde:
  yeniden olusturulan 9/9: sayfa, baslik, suzgec-paneli, suzgec-dugmesi, suzgec-listesi, olcum-tablosu, satir-1, satir-2, satir-3
durum "suzgec-paneli" dugumunde:
  yeniden olusturulan 3/9: suzgec-paneli, suzgec-dugmesi, suzgec-listesi
```

Süzgeç panelinin açık olup olmadığı yalnız paneli ilgilendirir. Bu değer sayfa kökünde
tutulursa her açma kapama dokuz bileşeni yeniden çizer; panelde tutulursa üçünü.
Durumu onu kullanan en alt bileşende tutmaya **durumun yerelde tutulması (state
colocation)** denir ve varsayılan tercih budur.

Ters yön de gereklidir. Seçili ölçüm türünü hem süzgeç paneli hem ölçüm tablosu bilmek
zorundadır. Aynı değerin iki kopyası iki ayrı yuvada tutulursa ikisi kaçınılmaz olarak
ayrışır. Böyle bir değer, iki bileşenin **ortak atasında** tutulur ve aşağıya verilir;
buna Bileşen Kavramı dersinde **durumun yukarı taşınması** dendi. Ölçütü tek cümleyle yazmak
olanaklıdır: durum, ona ihtiyaç duyan bütün bileşenlerin en yakın ortak atasında durur —
ne daha yukarıda, ne daha aşağıda.

#### Başlangıç Değeri ve Tembel Kurulum

Modeldeki kanca başlangıç değerini yalnız yuva ilk açılırken kullanır; sonraki
oluşturmalarda o değer hiç okunmaz. Bunun bir sonucu vardır: başlangıç değeri pahalı bir
hesaptan geliyorsa — yerel depolamadan okuma, uzun bir listeyi ayrıştırma — hesap her
oluşturmada boşuna yapılır. Kanca değer yerine bir işlev de kabul ederse hesap yalnız ilk
oluşturmada çalışır. Buna **tembel kurulum (lazy initialization)** denir.

İkinci sonuç daha sık şaşırtır: üst bileşenden gelen bir değer başlangıç değeri olarak
verildiğinde, o değerin sonradan değişmesi yuvayı güncellemez. Yuva ilk oluşturmada
dondurulmuştur. Üstten gelen değeri izlemesi gereken bir görünüm için o değer durum
olmamalı, doğrudan kullanılmalıdır.

#### Özet

- Yerel durum, bileşen gövdesinde değil bileşen örneğindeki bir yuvada yaşar; gövde her
  oluşturmada yuvaları çağrı sırasına göre okur.
- Yazma işlevi atama yapmaz, kuyruğa bir güncelleme isteği koyar; bir oluşturma boyunca
  okunan değer sabit kalır.
- Bir turdaki bütün güncellemeler toplu işlenir ve tek oluşturma üretir; ayrı turlardaki
  güncellemeler ayrı oluşturmalar üretir.
- Yeni değer eski değere bağlıysa güncelleyici işlev yazılır; doğrudan değer yazmak aynı
  turdaki ikinci güncellemede eskimiş değer üretir.
- Durum kancaları koşulsuz ve döngüsüz çağrılır, çünkü yuvalar ada göre değil sıraya göre
  bulunur.
- Durum, onu kullanan bileşenlerin en yakın ortak atasında tutulur; daha yukarıda tutmak
  gereksiz oluşturma, iki yerde tutmak ayrışma üretir.

#### Sonraki Adım

Bu dersin kurduğu döngü kendi içine kapalıdır: durum değişir, gövde yeniden çalışır, ağaç
uzlaşır. Gövdenin bu döngüde tek bir görevi vardır ve o da girdilerden bir çıktı
hesaplamaktır. Oysa ölçüm istasyonu sayfasının yapması gereken işlerin bir bölümü bu
hesabın dışındadır: sunucudan ölçüm çekmek, kaydırma olayına dinleyici bağlamak, seçili
süzgeci yerel depolamaya yazmak, bir zamanlayıcı kurmak. Bunlar gövdenin içine
yazılamaz — gövde bir çıktı hesaplar, dış dünyaya dokunmaz. Sonraki ders bu işlerin nereye
yazıldığını, ne zaman çalıştığını ve kurdukları şeyi ne zaman sökmek gerektiğini ele alır.

### Yan Etkiler

Source: https://academia.sh/tr/kurslar/bilesen-tabanli-gelistirme/yan-etkiler

Bileşenin dış dünyayla eşzamanlanması; oluşturmanın saflığı, yan etki kaydının belgeye yazma aşamasından sonra çalışması, bağımlılık listesinin karşılaştırılması, temizleme işlevinin sırası ve eşzamansız etkilerde yarış koşulu.

Önceki ders bileşen gövdesinin tek bir işi olduğunu kurdu: durumdan ve girdilerden bir
çıktı hesaplamak. Ölçüm istasyonu sayfasının yapması gereken işlerin bir bölümü bu hesabın
dışında kalır. Sunucudan ölçüm çekmek, ölçüm akışına abone olmak, seçili süzgeci yerel
depolamaya yazmak, belge başlığını değiştirmek, bir zamanlayıcı kurmak — hiçbiri bir çıktı
hesabı değildir. Bunlar programın dışarıya dokunduğu noktalardır ve Programlama Temelleri
kursunda tanıtılan adla **yan etki (side effect)** taşırlar.

Bu derste sorulan soru, bu işlerin nereye yazılacağıdır. Yanıt bir kayıt mekanizmasıdır:
gövde yan etkiyi yapmaz, yapılacak işi bildirir. Çalışma zamanı bu bildirimi alır, doğru
anda çalıştırır ve — bu dersin en çok atlanan yarısı — kurulan şeyi doğru anda söker.

#### Oluşturmanın Saflığı

Yan etkinin gövdeye yazılamamasının üç ayrı gerekçesi vardır ve üçü de gövdenin bir
sözleşmeye bağlı olmasından gelir: aynı girdilerle çağrılan gövde aynı çıktıyı üretmeli ve
başka hiçbir şey yapmamalıdır.

Birincisi zamanlamadır. Gövdenin ürettiği ağaç henüz belgeye yazılmamıştır; uzlaşma
sonucundaki fark daha uygulanmamıştır. Gövde içinde bir düğümü ölçmeye ya da odaklamaya
çalışan kod, olmayan bir düğümle konuşur.

İkincisi iptal edilebilirliktir. Bir oluşturma yarıda kesilebilir; çalışma zamanı daha
öncelikli bir güncelleme gelince başlanmış hesabı bırakıp baştan başlayabilir. Gövde
içinde gönderilen bir istek, sonucu hiç kullanılmayacak bir istektir; gövde içinde artan
bir sayaç iki kez artmış olabilir.

Üçüncüsü çoğaltmadır. Aynı gövde, geliştirme sırasındaki denetimler ya da bir sunucu
tarafı oluşturma turu nedeniyle iki kez çalıştırılabilir. Saf bir gövde için bu görünmez
bir işlemdir; abonelik açan bir gövde için iki aboneliktir.

Bu üç gerekçe tek bir kurala iner: **gövde okur ve hesaplar; dışarıya dokunan her iş
kayıtla bildirilir.**

#### Yan Etki Kaydı ve Bağımlılık Listesi

Gövdenin bildirdiği işe **yan etki kaydı (effect)** denir. Kayıt iki parçadan oluşur:
çalışacak işlev ve bir **bağımlılık listesi (dependency list)**. Çalışma zamanı, hesaplanan
farkı belgeye uyguladıktan sonra — bu aşamaya bu kursta **belgeye yazma aşaması (commit)**
denir — kayıtları gözden geçirir ve listesi bir önceki oluşturmadakinden farklı olanları
çalıştırır.

Karşılaştırma öge tek öge kimlik karşılaştırmasıdır; derin bir eşitlik sınaması değildir.
Bunun sonuçları bu dersin sonunda ele alınır.

```js
// yan-etki.mjs — bagimlilik listesi, calisma kosulu ve temizleme sirasi
let etkin = null;
const yaz = (s) => console.log("   " + s);

function yanEtkiKaydi(islev, bagimliliklar) {
  etkin.bekleyen.push({ i: etkin.imlec++, islev, bagimliliklar });
}

// Liste verilmemisse her islemede calisir; verilmisse ogeler tek tek karsilastirilir.
function ayniMi(a, b) {
  if (a === undefined || b === undefined) return false;
  return a.length === b.length && a.every((v, k) => Object.is(v, b[k]));
}

function olustur(o, ozellikler) {
  etkin = o; o.imlec = 0; o.bekleyen = [];
  console.log(`olusturma: istasyon=${ozellikler.istasyon}`);
  o.govde(ozellikler);
  etkin = null;
  for (const kayit of o.bekleyen) {           // islem asamasi: cizimden sonra
    const onceki = o.etkiler[kayit.i];
    if (onceki && ayniMi(onceki.bagimliliklar, kayit.bagimliliklar)) continue;
    if (onceki && onceki.temizle) onceki.temizle();
    kayit.temizle = kayit.islev() || null;
    o.etkiler[kayit.i] = kayit;
  }
}

function sok(o) {
  console.log("agactan cikarma");
  for (const k of o.etkiler) if (k && k.temizle) k.temizle();
  o.etkiler = [];
}

const panel = { etkiler: [], imlec: 0, bekleyen: [] };
panel.govde = ({ istasyon }) => {
  yanEtkiKaydi(() => { yaz("A calisti (liste yok)"); });
  yanEtkiKaydi(() => {
    yaz("B calisti (bos liste)");
    return () => yaz("B temizlendi");
  }, []);
  yanEtkiKaydi(() => {
    yaz(`C calisti: ${istasyon} akisina abone`);
    return () => yaz(`C temizlendi: ${istasyon} aboneligi kesildi`);
  }, [istasyon]);
};

olustur(panel, { istasyon: "KY-01" });
olustur(panel, { istasyon: "KY-01" });
olustur(panel, { istasyon: "KY-02" });
sok(panel);
```

```
olusturma: istasyon=KY-01
   A calisti (liste yok)
   B calisti (bos liste)
   C calisti: KY-01 akisina abone
olusturma: istasyon=KY-01
   A calisti (liste yok)
olusturma: istasyon=KY-02
   A calisti (liste yok)
   C temizlendi: KY-01 aboneligi kesildi
   C calisti: KY-02 akisina abone
agactan cikarma
   B temizlendi
   C temizlendi: KY-02 aboneligi kesildi
```

Listenin üç biçimi çıktıda ayrı ayrı okunur.

**Liste verilmemişse** etki her belgeye yazma aşamasından sonra çalışır. A kaydı üç oluşturmanın
üçünde de çalışmıştır. Bu biçim, eşzamanlanacak değer belirsiz olduğunda seçilir ve pahalıdır.

**Boş liste** verilmişse etki yalnız ilk oluşturmadan sonra çalışır. B kaydı bir kez
çalışmış ve yalnız ağaçtan çıkarılırken temizlenmiştir. Bileşenin ömrü boyunca bir kez
kurulacak işler — bir gözlemci kurmak, belgeye bir dinleyici bağlamak — bu biçimi kullanır.

**Değer içeren liste** verilmişse karşılaştırma sonucu belirleyicidir. İkinci oluşturmada
istasyon değişmediği için C kaydı hiç çalışmamıştır; üçüncüde değiştiği için çalışmıştır.

#### Temizleme

Yan etkinin ikinci yarısı, kurduğu şeyi sökmesidir. Kayıt işlevi bir işlev döndürürse, bu
işlev **temizleme işlevi (cleanup function)** olarak saklanır ve iki anda çağrılır: etki
yeniden çalışmadan hemen önce ve bileşen ağaçtan çıkarılırken.

Üçüncü oluşturmadaki sıra bu yüzden önemlidir. Önce eski abonelik kesilmiş, sonra yenisi
açılmıştır. Ters sıra iki aboneliğin bir an için birlikte var olması demek olurdu; abone
sayısını sayan bir sunucu için bu ölçülebilir bir farktır. Temizlemenin atlanması ise
istasyon her değiştiğinde bir abonelik biriktirir. Bu birikim iki maliyet üretir: kapalı
kalmayan bağlantılar ve — Eşzamansız JavaScript ve Çalışma Zamanı kursunda tanımlanan
sızıntı — artık ağaçta olmayan bir bileşene erişim tutan geri çağırımlar.

Ağaçtan çıkarmada iki temizleme de çalışmıştır. Bu, temizlemenin sözleşmesini tanımlar:
**etkinin kurduğu her şeyin karşılığı temizlemede sökülmelidir.** Dinleyici bağlayan etki
dinleyiciyi kaldırır, zamanlayıcı kuran etki zamanlayıcıyı iptal eder, abone olan etki
aboneliği keser. Temizlemenin kendisi de Kabuk Programlama kursundaki temizlik kancaları
gibi yinelemeye dayanıklı yazılır; zaten kapalı bir bağlantıyı ikinci kez kapatmak hata
üretmemelidir.

#### Eşzamansız Etkide Yarış

Temizlemenin en az anlaşılan kullanımı ağ isteklerindedir. Bir istek iptal edilemese bile
sonucunun kullanılması engellenebilir.

```js
// yaris.mjs — eszamansiz yan etkide yanit sirasinin bozulmasi
function kanal() {
  const bekleyen = new Map();
  return {
    getir: (ad) => new Promise((coz) => bekleyen.set(ad, coz)),
    yanitla: (ad) => bekleyen.get(ad)(`${ad} olcumleri`),
  };
}
const bosalt = () => new Promise((coz) => setTimeout(coz, 0));

async function senaryo(koruma) {
  console.log(koruma ? "temizleme var:" : "temizleme yok:");
  const ag = kanal();
  let gorunum = "-";
  let temizle = null;

  // Bagimlilik listesi [istasyon] degistiginde calisan yan etki.
  const etki = (istasyon) => {
    if (temizle) temizle();                 // onceki etkinin temizlenmesi
    let iptal = false;
    ag.getir(istasyon).then((veri) => {
      if (koruma && iptal) { console.log(`  ${veri} geldi, atildi`); return; }
      gorunum = veri;
      console.log(`  gorunum <- ${veri}`);
    });
    temizle = () => { iptal = true; };
  };

  etki("KY-01");              // kullanici once KY-01 seciyor
  etki("KY-02");              // hemen ardindan KY-02 seciyor
  ag.yanitla("KY-02");        // yakin istasyon once yanitladi
  await bosalt();
  ag.yanitla("KY-01");        // uzak istasyon sonra yanitladi
  await bosalt();
  console.log(`  secili istasyon KY-02, ekranda: ${gorunum}`);
}

await senaryo(false);
await senaryo(true);
```

```
temizleme yok:
  gorunum <- KY-02 olcumleri
  gorunum <- KY-01 olcumleri
  secili istasyon KY-02, ekranda: KY-01 olcumleri
temizleme var:
  gorunum <- KY-02 olcumleri
  KY-01 olcumleri geldi, atildi
  secili istasyon KY-02, ekranda: KY-02 olcumleri
```

Kullanıcı iki istasyonu hızlı seçmiş ve ikinci istek birinciden önce yanıtlanmıştır. Bu bir
**yarış koşuludur (race condition)**: sonuç, isteklerin gönderilme sırasına değil dönüş
sırasına bağlı kalmıştır. Temizleme olmayan durumda ekranda seçili olmayan istasyonun
ölçümleri durur ve hiçbir hata iletisi yoktur.

Çözüm, her etki çalışmasının kendi geçerlilik bayrağını taşımasıdır. Temizleme bayrağı düşürür;
geç gelen yanıt bayrağı sınayıp kendini atar. Aynı kalıbın taşınabilir biçimi Eşzamansız
JavaScript ve Çalışma Zamanı kursundaki iptal işaretiyle kurulur: temizleme isteği iptal eder ve
geç gelen yanıt hiç üretilmez.

#### Üç Yaygın Hata

**Eksik bağımlılık.** Listeye yazılmayan bir değer, etkinin ilk çalıştığı oluşturmadaki
kapanışta donar. Etki yeniden çalışmadığı için o değer hiç güncellenmez; bu **eskimiş
kapanıştır (stale closure)**. Kural tektir: etkinin gövdesinde okunan her değişken değer
listeye girer. Listeyi kısaltarak etkinin çalışma sayısını azaltmak, hatayı gizlemekten
başka bir şey yapmaz.

**Her oluşturmada yeniden üretilen bağımlılık.** Karşılaştırma kimlik karşılaştırması
olduğu için, gövde içinde yeniden kurulan bir nesne ya da dizi her oluşturmada farklı
görünür ve etki her seferinde çalışır. Bu, listeyi hiç vermemekle aynı sonuca varır.

**Durumu yan etkiyle türetmek.** Bir değeri başka bir değerden hesaplayıp sonucu duruma
yazan etki, tek bir kullanıcı eyleminden iki oluşturma üretir ve arada tutarsız bir kare
gösterir. Bu hata o kadar yaygındır ki sonraki dersin tamamı buna ayrılmıştır.

#### Özet

- Bileşen gövdesi saftır: okur ve hesaplar; dışarıya dokunan iş gövdede yapılmaz, kayıtla
  bildirilir.
- Yan etki kaydı, belgeye yazma aşamasından sonra çalışır; bağımlılık listesi bir önceki
  oluşturmanınkiyle öge tek öge kimlik karşılaştırmasına sokulur.
- Liste verilmemişse etki her yazma aşamasından sonra, boş listeyle yalnız ilk kez,
  değerli listeyle yalnız değerler değiştiğinde çalışır.
- Temizleme işlevi etki yeniden çalışmadan önce ve ağaçtan çıkarmada çağrılır; etkinin
  kurduğu her şeyin karşılığı temizlemede sökülmelidir.
- Eşzamansız etkilerde yanıt sırası istek sırasından farklı olabilir; temizlemenin
  düşürdüğü bir geçerlilik bayrağı geç gelen yanıtı elemenin en küçük yoludur.
- Eksik bağımlılık eskimiş kapanış, her oluşturmada yeniden kurulan nesne ise sürekli
  çalışan etki üretir.

#### Sonraki Adım

Yan etki kaydı dış dünyayla eşzamanlama içindir; ne var ki en sık kurulduğu yer dış dünya
değildir. Süzgeç panelinde seçili tür değiştiğinde ölçüm listesini süzen, sonucu bir duruma
yazan ve böylece ikinci bir oluşturma tetikleyen kod yaygın bir kalıptır — ve yanlıştır.
Süzülmüş liste bir durum değil, iki durumdan hesaplanabilen bir sonuçtur; onu ayrı bir
yuvada tutmak ikinci bir doğruluk kaynağı yaratır. Sonraki ders bu ayrımı kurar: hangi
değerler saklanır, hangileri her oluşturmada hesaplanır ve hesap pahalıysa sonucun
saklanmasının ölçülebilir kazancı nedir.

### Türetilmiş Değerler

Source: https://academia.sh/tr/kurslar/bilesen-tabanli-gelistirme/turetilmis-degerler

Başka değerlerden hesaplanabilen sonucun durumda tutulmaması; tek doğruluk kaynağı ölçütü, türetmeyi duruma yazmanın bedelleri, bellemenin yeniden hesap sayısına etkisi, kimlik kararlılığının aşağı akışta ürettiği fark ve bellemenin kendi maliyeti.

Önceki ders yan etki kaydını dış dünyayla eşzamanlama aracı olarak kurdu ve son cümlesinde
bir uyarı bıraktı: en sık kurulduğu yer dış dünya değildir. Süzgeç panelinde seçili tür
değiştiğinde ölçüm listesini süzen, sonucu ayrı bir yuvaya yazan ve böylece ikinci bir
oluşturma tetikleyen kod yaygındır. Bu ders o kalıbın neden yanlış olduğunu, yerine ne
konduğunu ve hesabın gerçekten pahalı olduğu durumda ne yapıldığını kurar.

Ayrım tek bir soruya iner: bu değer, elimdeki başka değerlerden hesaplanabiliyor mu?
Hesaplanabiliyorsa o bir durum değil, bir **türetilmiş değerdir (derived value)**.

#### Tek Doğruluk Kaynağı

Ölçüm istasyonu sayfasında beş değer dolaşır: sunucudan gelen ölçüm listesi, süzgeç
panelinde seçili tür, tablonun sıralandığı sütun, süzülmüş ölçüm listesi ve tablonun
altındaki "42 ölçümden 17'si gösteriliyor" sayısı.

İlk üçü durumdur; hiçbiri diğerlerinden hesaplanamaz. Son ikisi ise tümüyle ilk üçünden
çıkar: süzülmüş liste ölçümlerin ve seçili türün bir işlevidir, sayı da süzülmüş listenin
uzunluğudur. Bu iki değeri ayrı yuvalarda tutmak, aynı bilgiyi iki yerde saklamak olur.

Aynı bilginin tek bir yerde tutulmasına **tek doğruluk kaynağı (single source of truth)**
denir. Ölçüt sadedir: bir değeri saklamadan önce onu hesaplayıp hesaplayamayacağın sorulur.
Hesaplanabiliyorsa saklanmaz.

Bu ölçütün gizli bir sonucu vardır. Süzülmüş liste durumda tutulmadığında, ölçüm listesine
yeni bir satır eklendiğinde süzülmüş listeyi güncellemeyi **unutmak olanaksızdır**;
süzülmüş liste zaten yoktur, her oluşturmada yeniden hesaplanır. Türetilmiş değeri saklayan
tasarımda ise güncelleme sorumluluğu programcıya geçer ve unutulan her yol bir tutarsızlık
üretir.

#### Türetmeyi Duruma Yazmanın Üç Bedeli

Türetilmiş bir değeri bir yan etkiyle hesaplayıp duruma yazan kalıp üç ayrı bedel öder.

**Fazladan oluşturma.** Sıra şudur: kullanıcı türü değiştirir, durum güncellenir, gövde
çalışır, ağaç belgeye yazılır, yan etki çalışır, süzülmüş listeyi duruma yazar, gövde
yeniden çalışır, ağaç yeniden yazılır. Tek bir tıklama iki tam tur üretir.

**Tutarsız ara kare.** Bu iki turun arasında ekranda bir kare vardır ve o karede seçili tür
yenidir ama liste eskidir. Kullanıcı "sıcaklık" seçmişken bir an nem ölçümlerini görür. Bu
kare kısa olduğu için gözden kaçar; yavaş bir aygıtta kaçmaz.

**Ayrışma.** Ölçüm listesi başka bir yoldan da değişebilir — sunucudan yeni veri gelir,
bir satır silinir. Yan etkinin bağımlılık listesi bu yolu kapsamıyorsa süzülmüş liste eski
kalır ve iki kaynak kalıcı olarak ayrışır.

Doğru biçim, hesabı gövdenin içine koymaktır. Gövde her oluşturmada süzülmüş listeyi
yeniden hesaplar; ayrı yuva, ayrı oluşturma, ara kare ve ayrışma birden ortadan kalkar.
Kalan tek soru hesabın maliyetidir.

#### Belleme

Süzme ve sıralama, birkaç yüz satır için ölçülemeyecek kadar ucuzdur. Onbinlerce satır,
her satır için bir tarih ayrıştırma ya da bir birim dönüşümü söz konusu olduğunda hesap
görünür hale gelir. Bu durumda hesabın kendisi değil, **gereksiz tekrarı** hedef alınır.

Türetilmiş değerin, girdileri değişmediği sürece bir önceki sonucun saklanıp döndürülmesine
**belleme (memoization)** denir. Programlama Temelleri kursunda özyinelemeli hesapları
hızlandırmak için "not alma" adıyla tanıtılan tekniğin aynısıdır; buradaki fark, saklanan
girdinin bir bağımlılık listesi olmasıdır.

```js
// belleme.mjs — turetilmis degerin bellenmesi ve yeniden hesap sayisi
const OLCUMLER = [
  { kod: "KY-01", tur: "sicaklik", deger: -4.2 },
  { kod: "KY-02", tur: "nem", deger: 68 },
  { kod: "KY-03", tur: "sicaklik", deger: 1.5 },
  { kod: "KY-04", tur: "ruzgar", deger: 12.4 },
  { kod: "KY-05", tur: "sicaklik", deger: -0.8 },
];

let hesap = 0;
function suzVeSirala(olcumler, tur) {
  hesap++;
  return olcumler
    .filter((o) => tur === "hepsi" || o.tur === tur)
    .sort((a, b) => a.deger - b.deger)
    .map((o) => o.kod);
}

function bellenmis(yuvalar, i, hesapla, bagimliliklar) {
  const y = yuvalar[i];
  const ayni = y && y.b.length === bagimliliklar.length
    && y.b.every((v, k) => Object.is(v, bagimliliklar[k]));
  if (ayni) return y.d;
  const d = hesapla();
  yuvalar[i] = { b: bagimliliklar, d };
  return d;
}

// Turu degistirmeyen olusturmalar da vardir: panel acilip kapanmasi durumu degistirir.
const TURLAR = [
  ["hepsi", false], ["hepsi", true], ["hepsi", false],
  ["sicaklik", false], ["sicaklik", true], ["nem", true],
];

for (const belle of [false, true]) {
  hesap = 0;
  const yuvalar = [];
  console.log(belle ? "bellenmis:" : "her olusturmada hesaplanan:");
  for (const [tur, acik] of TURLAR) {
    const liste = belle
      ? bellenmis(yuvalar, 0, () => suzVeSirala(OLCUMLER, tur), [OLCUMLER, tur])
      : suzVeSirala(OLCUMLER, tur);
    console.log(`  tur=${tur.padEnd(8)} panel=${String(acik).padEnd(5)} liste=${liste.join(",")}`);
  }
  console.log(`  6 olusturmada ${hesap} hesap`);
}
```

```
her olusturmada hesaplanan:
  tur=hepsi    panel=false liste=KY-01,KY-05,KY-03,KY-04,KY-02
  tur=hepsi    panel=true  liste=KY-01,KY-05,KY-03,KY-04,KY-02
  tur=hepsi    panel=false liste=KY-01,KY-05,KY-03,KY-04,KY-02
  tur=sicaklik panel=false liste=KY-01,KY-05,KY-03
  tur=sicaklik panel=true  liste=KY-01,KY-05,KY-03
  tur=nem      panel=true  liste=KY-02
  6 olusturmada 6 hesap
bellenmis:
  tur=hepsi    panel=false liste=KY-01,KY-05,KY-03,KY-04,KY-02
  tur=hepsi    panel=true  liste=KY-01,KY-05,KY-03,KY-04,KY-02
  tur=hepsi    panel=false liste=KY-01,KY-05,KY-03,KY-04,KY-02
  tur=sicaklik panel=false liste=KY-01,KY-05,KY-03
  tur=sicaklik panel=true  liste=KY-01,KY-05,KY-03
  tur=nem      panel=true  liste=KY-02
  6 olusturmada 3 hesap
```

İki bölümün **listeleri satır satır aynıdır**; belleme sonucu değiştirmez, yalnız üretim
sayısını değiştirir. Altı oluşturmanın üçünde tür bir öncekiyle aynıdır — panelin açılıp
kapanması bir oluşturma üretir ama süzgeci ilgilendirmez — ve bellenmiş biçim bu üç turda
saklanan sonucu döndürmüştür.

Belleme yuvası tek bir girdi tutar. Son turdan sonra yeniden "hepsi" seçilseydi hesap
baştan yapılırdı, çünkü aradaki turlar yuvayı doldurmuştur. Bu bir eksiklik değil bilinçli
bir sınırdır: sınırsız bir önbellek her ayrı girdi bileşimi için bellek tutar ve tahliye
politikası gerektirir. Bir oluşturmadan diğerine geçen tek adımlık bir pencere, tipik
kullanımı zaten karşılar.

#### Kimlik Kararlılığı

Bellemenin ikinci gerekçesi hızdan değil kimlikten gelir ve daha az bilinir. Türetilmiş
değer bir dizi ya da nesneyse, her oluşturmada yeniden üretilen sonucun **içeriği aynı,
kimliği farklıdır**. Bu fark aşağıya doğru yayılır: sonucu alan bileşenin bağımlılık
listesi her oluşturmada değişmiş görünür.

```js
// kimlik.mjs — turetilmis degerin kimliginin asagi akista etkisi
const OLCUMLER = [
  { kod: "KY-01", tur: "sicaklik" },
  { kod: "KY-02", tur: "nem" },
  { kod: "KY-03", tur: "sicaklik" },
];

const sicakOlanlar = (kaynak) => kaynak.filter((o) => o.tur === "sicaklik");

function bellenmis(yuvalar, i, hesapla, bag) {
  const y = yuvalar[i];
  if (y && y.b.length === bag.length && y.b.every((v, k) => Object.is(v, bag[k]))) return y.d;
  const d = hesapla();
  yuvalar[i] = { b: bag, d };
  return d;
}

for (const belle of [false, true]) {
  const yuvalar = [];
  let oncekiListe = null;   // cocuk bilesenin bagimlilik listesinde tuttugu deger
  let etkiSayisi = 0;
  console.log(belle ? "bellenmis turetme:" : "her olusturmada yeniden turetme:");

  for (let n = 1; n <= 4; n++) {          // dort olusturma, girdiler hic degismiyor
    const liste = belle
      ? bellenmis(yuvalar, 0, () => sicakOlanlar(OLCUMLER), [OLCUMLER])
      : sicakOlanlar(OLCUMLER);
    const ayniNesne = Object.is(liste, oncekiListe);
    const derinAyni = oncekiListe !== null
      && liste.length === oncekiListe.length
      && liste.every((o, k) => o === oncekiListe[k]);
    if (!ayniNesne) etkiSayisi++;         // cocugun yan etkisi yeniden calisir
    console.log(
      `  olusturma ${n}: ayni nesne=${String(ayniNesne).padEnd(5)}`,
      `icerik ayni=${oncekiListe === null ? "-    " : String(derinAyni).padEnd(5)}`,
      `cocuk etkisi calisti=${!ayniNesne}`,
    );
    oncekiListe = liste;
  }
  console.log(`  4 olusturmada cocuk etkisi ${etkiSayisi} kez calisti`);
}
```

```
her olusturmada yeniden turetme:
  olusturma 1: ayni nesne=false icerik ayni=-     cocuk etkisi calisti=true
  olusturma 2: ayni nesne=false icerik ayni=true  cocuk etkisi calisti=true
  olusturma 3: ayni nesne=false icerik ayni=true  cocuk etkisi calisti=true
  olusturma 4: ayni nesne=false icerik ayni=true  cocuk etkisi calisti=true
  4 olusturmada cocuk etkisi 4 kez calisti
bellenmis turetme:
  olusturma 1: ayni nesne=false icerik ayni=-     cocuk etkisi calisti=true
  olusturma 2: ayni nesne=true  icerik ayni=true  cocuk etkisi calisti=false
  olusturma 3: ayni nesne=true  icerik ayni=true  cocuk etkisi calisti=false
  olusturma 4: ayni nesne=true  icerik ayni=true  cocuk etkisi calisti=false
  4 olusturmada cocuk etkisi 1 kez calisti
```

Dört oluşturmanın hiçbirinde girdi değişmemiştir. Bellenmemiş biçimde "içerik aynı"
sütunu her satırda doğru, "aynı nesne" sütunu her satırda yanlıştır: liste her seferinde
eşdeğer ama farklı bir dizidir. Aşağıdaki bileşenin yan etkisi bunu bir değişiklik sayar ve
dört kez çalışır. Bellenmiş biçimde bir kez çalışır.

Buna **kimlik kararlılığı (referential stability)** denir ve türetilmiş değer bir bileşen
sınırından geçiyorsa bellemenin asıl gerekçesidir. Karşılaştırmanın kimlik karşılaştırması
olması bir tasarım tercihidir: derin karşılaştırma büyük yapılarda hesabın kendisinden
pahalıya gelir. Bedeli, kimliği korumanın çağıranın sorumluluğuna geçmesidir.

Aynı gerekçe işlevler için de geçerlidir. Gövde içinde yazılan bir geri çağırım her
oluşturmada yeni bir işlev nesnesidir; aşağıya verildiğinde her seferinde değişmiş görünür.
İşlevi bellemek, dizi bellemekle aynı sorunu çözer.

#### Bellemenin Kendi Maliyeti

Belleme bedava değildir. Her bellenmiş değer bir yuva tutar, her oluşturmada bir liste
karşılaştırması yapılır ve saklanan sonuç bellekte kalır. Süzülmüş üç ögelik bir dizi için
bu maliyet hesabın kendisinden büyüktür.

Üç ölçüt karar için yeterlidir. Hesap gerçekten pahalıysa — ölçülmüş, tahmin edilmemiş —
bellenir. Sonuç bir bileşen sınırından geçiyor ve aşağıda bir bağımlılık listesine
giriyorsa bellenir. İkisi de yoksa bellenmez.

Ölçmeden belleme, okunması zor bir gövde ve ölçülemeyen bir kazanç üretir. Tarayıcı ve Web
Platformu kursundaki başarım kaydı, hangi hesabın gerçekten pahalı olduğunu söyleyen tek
kaynaktır; bağımlılık listeleri o kayda bakılarak eklenir.

#### Özet

- Başka değerlerden hesaplanabilen bir değer durum değildir; saklanmaz, her oluşturmada
  türetilir.
- Türetmeyi bir yan etkiyle duruma yazmak fazladan bir oluşturma, tutarsız bir ara kare ve
  kaynakların ayrışma riskini üretir.
- Belleme, bağımlılık listesi değişmediği sürece bir önceki sonucu döndürür; sonucu
  değiştirmez, yeniden hesap sayısını azaltır.
- Belleme yuvası tek adımlık bir pencere tutar; eski bir girdiye dönmek hesabı yeniden
  yaptırır.
- Kimlik kararlılığı, türetilmiş bir dizi ya da işlev bileşen sınırından geçtiğinde
  aşağıdaki bağımlılık listelerinin gereksiz yere değişmesini önler.
- Belleme yuva, karşılaştırma ve bellek maliyeti taşır; ölçülmemiş bir hesap için
  eklenmez.

#### Sonraki Adım

Türetilmiş değerler her oluşturmada yeniden hesaplanır, durum ise oluşturmalar arasında
yaşar ve değiştiğinde yeni bir oluşturma başlatır. Bu iki kategori arasında bir boşluk
vardır. Bir zamanlayıcının kimliği, bir isteğin sıra numarası, bir ögenin bir önceki
konumu, tabloyu kaydıran düğümün kendisi — hepsinin oluşturmalar arasında yaşaması gerekir,
ama hiçbirinin değişmesi ekranda bir değişiklik üretmez. Bunları duruma yazmak her
değişimde gereksiz bir oluşturma tetikler; yerel değişkende tutmak ise ikinci oluşturmada
kaybetmek demektir. Sonraki ders bu üçüncü kategoriyi tanımlar.

### Referanslar

Source: https://academia.sh/tr/kurslar/bilesen-tabanli-gelistirme/referanslar

Oluşturmalar arasında yaşayan ama değiştiğinde yeniden çizim başlatmayan değerler; saklama biçimlerinin iki ekseni, kutunun belgeye yazma aşamasında dolması, oluşturma sırasında kutuya dokunmama kuralı ve belge düğümüne erişimin bileşen sınırından geçirilmesi.

Önceki iki ders iki kategori kurdu. Durum, oluşturmalar arasında yaşar ve değiştiğinde
yeni bir oluşturma başlatır. Türetilmiş değer hiç saklanmaz, her oluşturmada yeniden
hesaplanır. Ölçüm istasyonu sayfasında bu iki kategoriye girmeyen değerler vardır.

Kaydırma olaylarını sayan bir sayaç, gönderilen isteğin sıra numarası, bir zamanlayıcının
kimliği, bir önceki oluşturmadaki süzgeç türü, tablonun kendisini tutan belge düğümü.
Hepsinin ortak yanı şudur: oluşturmalar arasında yaşamaları gerekir, ama değişmeleri
ekranda hiçbir şeyi değiştirmez. Bu ders üçüncü kategoriyi tanımlar ve ona **oluşturma
dışı başvuru (mutable reference)** der. Kısaca kutu denecektir: içinde tek bir alan
taşıyan, bileşen örneğine bağlı bir kap.

#### İki Eksen

Bir değeri nerede tutacağına karar vermek iki bağımsız soruya iner: değer bir sonraki
oluşturmada duruyor mu ve değişmesi bir oluşturma başlatıyor mu.

| Saklama biçimi | Oluşturmalar arası yaşar | Değişmesi oluşturma başlatır |
|---|---|---|
| Gövde içindeki yerel değişken | hayır | hayır |
| Durum yuvası | evet | evet |
| Oluşturma dışı başvuru | evet | hayır |
| Modül düzeyi değişken | evet | hayır |

Dört satırın üçü ayrı gereksinimi karşılar. Yerel değişken bir oluşturma içinde ara sonuç
tutmaya yarar. Durum yuvası ekranda görünen her şey içindir. Başvuru, yaşaması gereken ama
görünmeyen değerler içindir.

Dördüncü satır — modül düzeyi değişken — başvuruyla aynı iki cevabı verir ama bir farkı
vardır: bileşene değil modüle bağlıdır. Aynı bileşenden sayfada iki örnek varsa ikisi aynı
değişkeni paylaşır. Sayfada iki ölçüm tablosu bulunduğunda, kaydırma sayacının modül
düzeyinde tutulması iki tablonun sayaçlarını birbirine karıştırır. Başvuru örneğe bağlıdır;
her tablonun kendi kutusu olur.

#### Aynı Sayacı Üç Ayrı Yerde Tutmak

Fark ölçülebilir. Ölçüm tablosu kaydırma olaylarını sayar, ama ekrandaki göstergeyi her
beş olayda bir günceller. Aynı gereksinim üç saklama biçimiyle karşılanır.

```js
// saklama.mjs — ayni sayaci uc ayri yerde tutmanin sonucu
const OLAYLAR = ["k", "k", "k", "p", "k", "k", "k", "k", "k", "p", "k", "k"];
const ESIK = 5;

function senaryo(bicim) {
  let cizim = 1;                 // ilk olusturma
  let yerel = 0;                 // govde icindeki yerel degisken
  let durum = 0;                 // durum yuvasi
  const kutu = { deger: 0 };     // olusturma disi basvuru
  let ekranda = 0;

  const olustur = () => { cizim++; yerel = 0; };   // her olusturmada yerel sifirlanir

  for (const olay of OLAYLAR) {
    if (olay === "p") { olustur(); continue; }     // sayacla ilgisiz bir durum degisimi
    if (bicim === "yerel") {
      yerel++;
      if (yerel % ESIK === 0) { ekranda = yerel; olustur(); }
    } else if (bicim === "durum") {
      durum++;
      ekranda = durum;
      olustur();                                   // her yazim bir olusturma uretir
    } else {
      kutu.deger++;
      if (kutu.deger % ESIK === 0) { ekranda = kutu.deger; olustur(); }
    }
  }

  const sayac = bicim === "yerel" ? yerel : bicim === "durum" ? durum : kutu.deger;
  console.log(
    `${bicim.padEnd(5)} | sayac=${String(sayac).padStart(2)}`,
    `| ekranda=${String(ekranda).padStart(2)} | olusturma=${cizim}`,
  );
}

console.log("10 kaydirma + 2 ilgisiz durum degisimi; ekran her 5 kaydirmada bir guncelleniyor");
for (const b of ["yerel", "durum", "kutu"]) senaryo(b);
```

```
10 kaydirma + 2 ilgisiz durum degisimi; ekran her 5 kaydirmada bir guncelleniyor
yerel | sayac= 2 | ekranda= 5 | olusturma=4
durum | sayac=10 | ekranda=10 | olusturma=13
kutu  | sayac=10 | ekranda=10 | olusturma=5
```

Üç satır üç ayrı sonucu gösterir.

Yerel değişken sayıyı **kaybeder**. On kaydırma sayılmıştır ama sayaç ikide durmaktadır, çünkü
araya giren her oluşturma — sayacın kendi tetiklediği bir oluşturma ve süzgeç panelinden gelen iki
oluşturma — değişkeni sıfırlamıştır. Bu, yerel değişkenin bir eksikliği değil tanımıdır.

Durum yuvası doğru sayar, ama on üç oluşturma üretir. Sayacın her artışı bir oluşturma
demektir; oysa ekran bu artışların yalnız ikisinde değişmiştir. On bir oluşturmanın çıktısı
bir öncekiyle aynıdır.

Başvuru kutusu doğru sayar ve beş oluşturma üretir: ekranın gerçekten değiştiği iki
oluşturma, süzgeç panelinden gelen iki oluşturma ve ilk oluşturma. Bu, üçüncü kategorinin
neden var olduğunu tek satırda söyler.

#### Kutunun Ne Zaman Dolduğu

Başvurunun ikinci kullanımı, bileşenin ürettiği belge düğümüne erişmektir. Bir düğümü
odaklamak, ölçmek ya da bir gözlemciye vermek için o düğümün kendisi gerekir; oysa bileşen
gövdesi düğümleri değil, düğüm tarifini üretir. Bağ, çalışma zamanının kutuyu doldurmasıyla
kurulur.

```js
// kutu-ne-zaman-dolar.mjs — dugum kutusunun dolma ani ve "onceki deger" kalibi
const kutu = (baslangic) => ({ deger: baslangic });

const dugumKutusu = kutu(null);   // belgedeki tablo dugumu buraya yazilacak
const oncekiTur = kutu(null);     // bir onceki olusturmadaki tur

let cizim = 0;

function govde(tur) {
  cizim++;
  const degisti = oncekiTur.deger !== null && oncekiTur.deger !== tur;
  console.log(`olusturma ${cizim} (tur=${tur})`);
  console.log(`   govde okur -> dugum kutusu: ${dugumKutusu.deger}`);
  console.log(`   govde okur -> onceki tur  : ${oncekiTur.deger}, degisti: ${degisti}`);
  return { etiket: "table", anahtar: `tablo-${tur}` };
}

function belgeyeYaz(agac) {
  const dugum = `[dugum ${agac.etiket} ${agac.anahtar}]`;
  dugumKutusu.deger = dugum;      // kutuyu calisma zamani doldurur
  console.log(`   yazma asamasi -> kutu = ${dugum}`);
}

function yanEtkiler(tur) {
  console.log(`   yan etki okur -> ${dugumKutusu.deger} olculebilir`);
  oncekiTur.deger = tur;          // bir sonraki olusturma icin saklanir
}

for (const tur of ["sicaklik", "sicaklik", "nem"]) {
  const agac = govde(tur);
  belgeyeYaz(agac);
  yanEtkiler(tur);
}
```

```
olusturma 1 (tur=sicaklik)
   govde okur -> dugum kutusu: null
   govde okur -> onceki tur  : null, degisti: false
   yazma asamasi -> kutu = [dugum table tablo-sicaklik]
   yan etki okur -> [dugum table tablo-sicaklik] olculebilir
olusturma 2 (tur=sicaklik)
   govde okur -> dugum kutusu: [dugum table tablo-sicaklik]
   govde okur -> onceki tur  : sicaklik, degisti: false
   yazma asamasi -> kutu = [dugum table tablo-sicaklik]
   yan etki okur -> [dugum table tablo-sicaklik] olculebilir
olusturma 3 (tur=nem)
   govde okur -> dugum kutusu: [dugum table tablo-sicaklik]
   govde okur -> onceki tur  : sicaklik, degisti: true
   yazma asamasi -> kutu = [dugum table tablo-nem]
   yan etki okur -> [dugum table tablo-nem] olculebilir
```

Sıra üç aşamadır: gövde çalışır, ağaç belgeye yazılır, yan etkiler çalışır. Kutu ikinci
aşamada dolar. Bu yüzden ilk oluşturmanın gövdesinde kutu boştur — ölçülecek düğüm henüz
yoktur — ve yan etkide doludur. Düğümü ölçen, odaklayan ya da bir gözlemciye veren kod bu
nedenle yan etki kaydında yazılır.

Üçüncü oluşturmanın gövdesi ikinci bir ayrıntıyı gösterir: kutu, o anda **bir önceki
oluşturmanın** düğümünü taşımaktadır. Gövde çalışırken belgede hâlâ eski ağaç durur. Bu,
kutunun eskiliği değil aşamaların sırasıdır.

İkinci kutu farklı bir kalıbı kurar. Yan etkide yazılan, gövdede okunan bir kutu, bir
önceki oluşturmadaki değeri taşır ve karşılaştırmayı olanaklı kılar. Üçüncü oluşturmada
"degisti" doğru olmuştur: gövde, türün bu turda değiştiğini yalnız kutu sayesinde bilir.
Buna **önceki değer kalıbı** denir.

#### Oluşturma Sırasında Kutuya Dokunmamak

Bağlayıcı kural: gövde bir kutuyu ne okur ne yazar. Kutuya dokunmak yan etkilerde, olay
işleyicilerinde ve zamanlayıcı geri çağırımlarında serbesttir.

Gerekçe üç maddedir. Gövdenin sözleşmesi saflıktır ve kutuya yazmak bir yan etkidir. Gövde
yarıda kesilebilir; kesilen bir gövdenin kutuya yazdığı değer, hiç gösterilmeyen bir
oluşturmadan artakalır. Gövde çalışırken belge hâlâ bir önceki ağacı taşır; okunan düğüm
kutusu eski düğümü verir.

Kuralın tek istisnası, kutunun ilk değerini bir kez kurmaktır: kutu boşsa doldurup dolu
olduğunda dokunmamak, hiçbir oluşturmada farklı bir sonuç üretmediği için saflığı bozmaz.

#### Belge Düğümüne Erişim ve Bileşen Sınırı

Bir düğüme başvuru tutmanın meşru kullanımları sayılıdır: odağı bir ögeye taşımak, bir
ögenin ölçüsünü okumak, bir gözlemciyi hedefe bağlamak, bir medya ögesini oynatmak,
bileşenin denetimi dışında yazılmış bir kitaplığa bir kap vermek. Bu listenin dışında kalan
her şey — sınıf eklemek, metin yazmak, bir çocuğu gizlemek — bildirimsel yolla, yani
durumdan yapılır. Düğümü elle değiştiren kod, bir sonraki oluşturmada uzlaşmanın üzerine
yazdığı bir değişiklik üretir ve iki güncelleme kaynağı çakışır.

Başvurunun bileşen sınırından geçmesi ayrı bir tasarım kararıdır. Üst bileşen, alt bileşenin
ürettiği düğüme erişmek isteyebilir: süzgeç paneli kapandığında odağın tablodaki ilk
satıra dönmesi gerekir. Ham düğümü dışarı açmak kapsüllemeyi bozar; dışarıdaki kod artık
bileşenin iç yapısına — hangi etiketin kullanıldığına, kaç sarmalayıcı olduğuna — bağlanır.

Daha sağlam biçim, düğümü değil bir komut yüzeyini açmaktır: bileşen dışarıya `odakla` ve
`basaKaydir` gibi adlandırılmış işlemler verir, düğümü kendinde tutar. İç yapı değiştiğinde
yalnız bileşen değişir. Bu, Programlama Temelleri kursundaki arayüz–gerçekleştirim
ayrımının bileşenlere uygulanmış hâlidir.

#### Ne Zaman Durum, Ne Zaman Kutu

Karar tek soruyla verilir: **bu değer değiştiğinde ekranda bir şey değişmeli mi?**

Değişmeliyse o değer durumdur; kutuya yazmak ekranı sessizce eski bırakır ve hata
bulunması güç bir sınıf üretir — veri doğrudur, görünüm eskidir. Değişmemeliyse kutudur;
duruma yazmak her değişimde gereksiz bir oluşturma üretir.

İki ara durum sık karşılaşılır. Bir değer bazen ekranı ilgilendiriyorsa, ikiye bölünür:
ham sayaç kutuda, ekranda gösterilen eşik değeri durumda — yukarıdaki örnekteki gibi.
Bir değer yalnız olay işleyicisinde okunuyorsa kutudur; hiçbir oluşturma onu okumaz.

#### Özet

- Saklama biçimleri iki eksende ayrılır: oluşturmalar arasında yaşama ve değiştiğinde
  oluşturma başlatma.
- Oluşturma dışı başvuru yaşar ama oluşturma başlatmaz; modül düzeyi değişkenden farkı
  bileşen örneğine bağlı olmasıdır.
- Aynı sayaç yerel değişkende tutulduğunda kaybolur, durumda tutulduğunda gereksiz
  oluşturma üretir, kutuda tutulduğunda ikisi de olmaz.
- Kutu belgeye yazma aşamasında dolar; gövde çalışırken bir önceki oluşturmanın düğümünü
  taşır, bu yüzden düğüme dokunan kod yan etkide yazılır.
- Gövde kutuyu ne okur ne yazar; tek istisna kutunun ilk değerinin bir kez kurulmasıdır.
- Düğüm başvurusu odak, ölçüm ve dış kitaplık bağlama içindir; sınır dışına ham düğüm
  değil adlandırılmış komutlar açılır.

#### Sonraki Adım

Buraya kadar tanımlanan üç saklama biçimi de tek bir bileşenin içinde kalır. Değerin başka
bir bileşene ulaşması için tek yol vardır: üst bileşenden alta doğru verilmek. Ölçüm
istasyonu sayfasında bu yolun uzadığı bir durum vardır. Sıcaklık biriminin santigrat mı
fahrenhayt mı gösterileceği sayfanın en üstünde seçilir, ama bu bilgiye ihtiyaç duyan yer
en altta, tablonun içindeki ölçüm rozetidir. Aradaki dört bileşenin hiçbiri birimi
kullanmaz; yalnız taşır. Sonraki ders bu zinciri kısaltan mekanizmayı, ağaçta bir değer
sağlayıp aşağıdaki herhangi bir düğümde okumayı ele alır.

### Bağlam Aktarımı

Source: https://academia.sh/tr/kurslar/bilesen-tabanli-gelistirme/baglam-aktarimi

Bir değeri ağaçta sağlayıp aşağıdaki herhangi bir düğümde okumak; özellik zincirinin sorunu, sağlayıcının yukarı doğru çözümlenmesi, en yakın sağlayıcının gölgelemesi, sağlanan değerin kimliğinin tüketici sayısına etkisi ve bağlamın uygun olmadığı yerler.

Önceki dersin üç saklama biçimi de tek bir bileşenin içinde kalır. Değerin başka bir
bileşene ulaşması için bilinen tek yol vardır: üstten alta doğru özellik olarak verilmek.
Ölçüm istasyonu sayfasında bu yol uzar. Sıcaklığın santigrat mı fahrenhayt mı gösterileceği
sayfanın en üstünde seçilir; bu bilgiye ihtiyaç duyan yer en altta, tablo satırının içindeki
ölçüm rozetidir. Aradaki dört bileşen — sayfa gövdesi, ölçüm tablosu, tablo satırı, hücre —
birimi hiç kullanmaz, yalnız taşır.

Bu ders zinciri kısaltan mekanizmayı kurar: değeri ağacın bir düğümünde **sağlamak** ve
altındaki herhangi bir düğümde, aradakilere hiç dokunmadan **okumak**.

#### Özellik Zincirinin Sorunu

Bir değerin ara bileşenlerden geçirilerek taşınmasına **özellik zinciri (prop
drilling)** denir. Zincirin kendisi bir hata değildir; iki üç düzey için en açık çözüm
budur. Uzadıkça üç ayrı maliyet üretir.

Birincisi arayüz genişlemesidir. Ölçüm tablosunun API'sinde artık bir birim parametresi
vardır, oysa tablo birimle hiç ilgilenmez. Bileşenin dışarıdan görünen sözleşmesi, kendi
işini anlatmayan alanlarla dolar.

İkincisi değişim maliyetidir. Zincire yeni bir değer eklemek — yerel ayar, ondalık basamak
sayısı, eşik renkleri — aradaki her bileşende bir imza değişikliği demektir. Beş düzeyli bir
zincire üç değer eklemek on beş dosyaya dokunmaktır.

Üçüncüsü yeniden kullanımdır. Birimi taşımak zorunda olan bir tablo, birimin olmadığı bir
sayfada kullanılamaz; parametre isteğe bağlı yapıldığında ise varsayılanın nereden geleceği
sorusu doğar.

#### Sağlayıcı ve Çözümleme

Bağlam bu üç maliyeti ortadan kaldırır. Ağacın bir düğümü bir anahtar altında değer
**sağlar**; o düğümün altındaki herhangi bir bileşen aynı anahtarla değeri okur. Aradaki
bileşenlerin haberi olmaz.

Okuma bir arama işlemidir: bileşen kendi düğümünden başlar, ata zinciri boyunca yukarı
çıkar ve o anahtarı sağlayan ilk düğümde durur. Hiçbir düğüm sağlamıyorsa bağlamın
tanımıyla birlikte verilen varsayılan değer kullanılır.

```js
// baglam-cozumleme.mjs — saglayicinin agacta yukari dogru aranmasi
const dugum = (ad, saglanan, cocuklar = []) => {
  const d = { ad, saglanan, cocuklar, ust: null };
  for (const c of cocuklar) c.ust = d;
  return d;
};

const VARSAYILAN = { birim: "kelvin" };   // hicbir saglayici yoksa kullanilir

function coz(baslangic, anahtar) {
  let d = baslangic;
  let adim = 0;
  while (d) {
    if (d.saglanan && anahtar in d.saglanan)
      return { deger: d.saglanan[anahtar], saglayici: d.ad, adim };
    d = d.ust;
    adim++;
  }
  return { deger: VARSAYILAN[anahtar], saglayici: "(varsayilan)", adim };
}

const rozet1 = dugum("rozet-1");
const rozet2 = dugum("rozet-2");
const rozet3 = dugum("rozet-3");
const sayfa = dugum("sayfa", { birim: "santigrat" }, [
  dugum("baslik"),
  dugum("suzgec-paneli", null, [dugum("suzgec-listesi", null, [rozet3])]),
  dugum("olcum-tablosu", null, [
    dugum("satir-1", null, [rozet1]),
    dugum("karsilastirma-panosu", { birim: "fahrenhayt" }, [rozet2]),
  ]),
]);
const ayrikRozet = dugum("ayrik-rozet");   // agacin disinda

console.log(`kok: ${sayfa.ad}`);
for (const d of [rozet1, rozet2, rozet3, ayrikRozet]) {
  const s = coz(d, "birim");
  console.log(
    `  ${d.ad.padEnd(12)} birim=${s.deger.padEnd(10)}`,
    `kaynak=${s.saglayici.padEnd(21)} ${s.adim} ust dugum tarandi`,
  );
}
```

```
kok: sayfa
  rozet-1      birim=santigrat  kaynak=sayfa                 3 ust dugum tarandi
  rozet-2      birim=fahrenhayt kaynak=karsilastirma-panosu  1 ust dugum tarandi
  rozet-3      birim=santigrat  kaynak=sayfa                 3 ust dugum tarandi
  ayrik-rozet  birim=kelvin     kaynak=(varsayilan)          1 ust dugum tarandi
```

Dört satır dört ayrı davranışı gösterir. Birinci rozet üç düzey yukarıdaki sayfa
sağlayıcısını bulmuştur; aradaki tablo ve satır bileşenleri birimi hiç görmemiştir.

İkinci rozet karşılaştırma panosunun içindedir ve bir düzey yukarıdaki sağlayıcıda durur.
Sayfa sağlayıcısı hâlâ oradadır, ama aranmaz: **en yakın sağlayıcı kazanır.** Bu, aynı
sayfada bir bölümün farklı bir birimle gösterilmesini olanaklı kılar.

Üçüncü rozet ağacın bambaşka bir dalındadır ve yine sayfa sağlayıcısını bulur. Bağlam bir
dala değil, bir alt ağaca yayılır.

Dördüncü rozet hiçbir sağlayıcının altında değildir ve varsayılana düşer. Varsayılanın
anlamlı seçilmesi bir tasarım işidir: bağlam olmadan da çalışan bir bileşen, tek başına
sınanabilir ve başka bir sayfada yeniden kullanılabilir.

#### Bağlam Sözcüksel Değil, Konumsaldır

Bu mekanizmanın en çok yanlış anlaşılan yanı, benzediği şeyden ayrıldığı noktadır.
Programlama Temelleri kursundaki kapsam **sözcükseldir**: bir işlevin gördüğü değişkenler,
o işlevin kaynak kodda nerede yazıldığına bağlıdır. Bağlam ise **konumsaldır**: bir
bileşenin gördüğü değer, o bileşenin ağaçta nereye yerleştirildiğine bağlıdır.

Aynı ölçüm rozeti kaynakta bir kez yazılır; tablonun içinde santigrat, karşılaştırma
panosunun içinde fahrenhayt okur. Kaynak kod değişmemiştir; yer değişmiştir.

En yakın sağlayıcının kazanması, aynı kursta tanımlanan **gölgelemenin** ağaç üzerindeki
karşılığıdır: iç sağlayıcı, dıştakini kendi alt ağacında görünmez kılar. Bu güç bir
maliyetle gelir. Bir bileşenin hangi değeri okuyacağını anlamak için tanımına bakmak
yetmez; ağaçtaki yerini bilmek gerekir. Bu yüzden bağlamla taşınan değerler az sayıda ve
adları açık olmalıdır.

#### Değişimin Maliyeti

Sağlanan değer değiştiğinde, o bağlamı okuyan bütün bileşenler yeniden çizilir —
ağaçta nerede olurlarsa olsunlar, aradaki bileşenler değişmemiş olsa bile. Sağlanan değerin
kimliği bu yüzden doğrudan bir maliyet kalemidir.

```js
// baglam-maliyeti.mjs — saglanan degerin kimliginin tuketici sayisina etkisi
const BIRIM_TUKETICI = 6;    // birimi okuyan bilesen sayisi
const SATIR_TUKETICI = 2;    // secili satiri okuyan bilesen sayisi

const OLAYLAR = [
  { ad: "panel acildi",    birim: "santigrat",  secili: null },
  { ad: "satir 1 secildi", birim: "santigrat",  secili: 1 },
  { ad: "satir 2 secildi", birim: "santigrat",  secili: 2 },
  { ad: "birim degisti",   birim: "fahrenhayt", secili: 2 },
];

function calistir(strateji) {
  let oncekiBirim = "santigrat";     // ilk olusturmada kurulan degerler
  let oncekiSecili = null;
  let toplam = 0;
  console.log(`${strateji}:`);
  for (const olay of OLAYLAR) {
    let sayi = 0;
    if (strateji === "govdede kurulan nesne") {
      sayi = BIRIM_TUKETICI + SATIR_TUKETICI;          // kimlik her olusturmada degisir
    } else if (strateji === "bellenmis tek baglam") {
      const degisti = olay.birim !== oncekiBirim || olay.secili !== oncekiSecili;
      sayi = degisti ? BIRIM_TUKETICI + SATIR_TUKETICI : 0;
    } else {
      if (olay.birim !== oncekiBirim) sayi += BIRIM_TUKETICI;
      if (olay.secili !== oncekiSecili) sayi += SATIR_TUKETICI;
    }
    oncekiBirim = olay.birim;
    oncekiSecili = olay.secili;
    toplam += sayi;
    console.log(`  ${olay.ad.padEnd(15)} -> yeniden cizilen tuketici: ${sayi}`);
  }
  console.log(`  toplam: ${toplam}`);
}

console.log(`${BIRIM_TUKETICI} birim tuketicisi, ${SATIR_TUKETICI} satir tuketicisi, 4 olay`);
for (const s of ["govdede kurulan nesne", "bellenmis tek baglam", "iki ayri baglam"])
  calistir(s);
```

```
6 birim tuketicisi, 2 satir tuketicisi, 4 olay
govdede kurulan nesne:
  panel acildi    -> yeniden cizilen tuketici: 8
  satir 1 secildi -> yeniden cizilen tuketici: 8
  satir 2 secildi -> yeniden cizilen tuketici: 8
  birim degisti   -> yeniden cizilen tuketici: 8
  toplam: 32
bellenmis tek baglam:
  panel acildi    -> yeniden cizilen tuketici: 0
  satir 1 secildi -> yeniden cizilen tuketici: 8
  satir 2 secildi -> yeniden cizilen tuketici: 8
  birim degisti   -> yeniden cizilen tuketici: 8
  toplam: 24
iki ayri baglam:
  panel acildi    -> yeniden cizilen tuketici: 0
  satir 1 secildi -> yeniden cizilen tuketici: 2
  satir 2 secildi -> yeniden cizilen tuketici: 2
  birim degisti   -> yeniden cizilen tuketici: 6
  toplam: 10
```

Birinci strateji, sağlanan değeri sağlayıcının gövdesinde her oluşturmada yeniden kuran
yaygın yazımdır. Değerin içeriği değişmese bile kimliği değişir, dolayısıyla panelin
açılması bile sekiz tüketiciyi yeniden çizer. Bu, önceki derste kurulan kimlik
kararlılığı sorununun bağlamdaki karşılığıdır ve çözümü aynıdır: sağlanan değer bellenir.

İkinci strateji bunu yapar ve ilgisiz oluşturmaları eler; otuz iki tüketici oluşturması
yirmi dörde iner. Ama seçili satır her değiştiğinde birimi okuyan altı bileşen de yeniden
oluşturulur, çünkü ikisi tek bir nesnede taşınmaktadır.

Üçüncü strateji değerleri ayırır: seyrek değişen birim bir bağlamda, sık değişen seçim
başka bir bağlamda. Toplam ona iner. Kural buradan çıkar: **bir bağlamda birlikte taşınan
değerler aynı sıklıkta değişmelidir.** Değişim sıklıkları ayrışıyorsa bağlam bölünür.

#### Bağlamın Uygun Olmadığı Yerler

Bağlam bir durum yönetimi çözümü değildir; bir taşıma yoludur. Değer yine bir yerde durum olarak
tutulur; bağlam yalnız onun ağaçta dolaşmasını sağlar.

Uygun olduğu değerler ortak bir özellik taşır: seyrek değişirler ve ağacın geniş bir
bölümünü ilgilendirirler. Görsel tema, ölçü birimi, yerel ayar, oturum açmış kullanıcı
bilgisi, biçim ayarları, erişilebilirlik tercihleri bu tanıma uyar.

Uygun olmadığı yerler de bellidir. Sık değişen bir liste — kaydırma konumu, imleç
altındaki satır, bir animasyonun ilerleme değeri — bağlamla taşındığında her değişimde
bütün tüketicileri yeniden çizer. İki bileşen arasında paylaşılan tek bir değer için
bağlam kurmak, önceki dersteki durumun yukarı taşınmasından daha karmaşık ve daha
maliyetlidir.

Son bir ölçüt sınanabilirliktir. Bağlam okuyan bir bileşen tek başına oluşturulduğunda
varsayılan değere düşer. Varsayılan anlamlıysa bileşen bağımsız olarak sınanabilir;
varsayılan yoksa ve bağlamın bulunmaması hata üretiyorsa, bileşen her sınamada bir
sağlayıcıyla sarmalanmak zorunda kalır.

#### Özet

- Özellik zinciri uzadıkça ara bileşenlerin arayüzünü genişletir, değişim
  maliyetini artırır ve yeniden kullanımı zorlaştırır.
- Bağlam okuma, tüketici düğümden ata zinciri boyunca yukarı çıkıp o anahtarı sağlayan ilk
  düğümde duran bir aramadır; hiçbiri yoksa varsayılan kullanılır.
- En yakın sağlayıcı kazanır; iç sağlayıcı dıştakini kendi alt ağacında gölgeler.
- Bağlam sözcüksel değil konumsaldır: bileşenin gördüğü değer, tanımının yerine değil
  ağaçtaki konumuna bağlıdır.
- Sağlanan değer her oluşturmada yeniden kurulursa bütün tüketiciler her oluşturmada
  yeniden çizilir; değer bellenir.
- Bir bağlamda birlikte taşınan değerler aynı sıklıkta değişmelidir; sıklıklar ayrışıyorsa
  bağlam bölünür.

#### Sonraki Adım

Bağlam bir değeri ağaçta aşağı doğru akıtır. Ters yönde akan bir şey daha vardır ve bu
derse kadar hiç ele alınmadı: hata. Ölçüm tablosunun bir satırı, sunucudan gelen bozuk bir
kayıt yüzünden oluşturma sırasında bir istisna atarsa ne olur? Bildirimsel bir ağaçta bu
sorunun yanıtı sanıldığından serttir: yarım kalmış bir ağaç tutarsızdır ve çalışma zamanının
elinde onu kurtaracak bir bilgi yoktur. Sonraki ders bu davranışın gerekçesini, bir alt
ağacın hatasını çevresinden yalıtan mekanizmayı ve bu mekanizmanın yakalayamadığı hata
türlerini ele alır.

### Hata Sınırları

Source: https://academia.sh/tr/kurslar/bilesen-tabanli-gelistirme/hata-sinirlari

Oluşturma sırasında atılan bir hatanın bileşen ağacında yalıtılması; yarım ağacın neden bırakılmadığı, en yakın sınırın yakalaması, sınır yerleşiminin ekranda kalan bileşen sayısına etkisi, çağrı yığınının dışına çıkan hataların kapsam dışı kalması ve kurtarma.

Önceki ders bir değeri ağaçta aşağı doğru akıtan mekanizmayı kurdu. Ters yönde akan bir
şey daha vardır ve bu derse kadar hiç ele alınmadı: hata.

Ölçüm tablosunun bir satırı, sunucudan gelen bir kayıtta birim alanı bulunmadığı için
oluşturma sırasında bir istisna atsın. Elle yazılmış bir arayüzde bu, o satırın boş
kalmasıdır. Bildirimsel bir ağaçta yanıt daha serttir: varsayılan davranış, ağacın tamamının
sökülüp ekranın boşalmasıdır. Bu ders önce bu davranışın gerekçesini kurar, sonra hatayı
bir alt ağaçta tutmanın yolunu, en sonda da bu yolun kapsamadığı hata türlerini gösterir.

#### Yarım Ağaç Neden Bırakılmaz

Uzlaşma, bir oluşturmanın ürettiği ağacın **tamamını** bir öncekiyle karşılaştırır ve farkı
tek bir yazma aşamasında uygular. Ortada bir istisna atıldığında elde iki şey vardır:
kısmen hesaplanmış yeni bir ağaç ve belgede duran eski ağaç.

Çalışma zamanının bu noktada kısmi ağacı yazması, hiçbir bileşenin sözleşmesinin
geçerliliğini bilmediği bir durumu ekrana koymak olur. Tablo başlığı yeni süzgece göre
"sıcaklık ölçümleri" der, satırlar ise eski süzgeçten kalmış nem değerlerini gösterir. Bir ölçüm
istasyonu sayfasında bu, boş bir ekrandan daha kötüdür: kullanıcı yanlış veriyi doğru
sanır.

Ayrıca hatanın nereye kadar yayıldığı bilinmez. Atan bileşen bir yan etki kaydetmiş, bir
kutuya yazmış, bir aboneliğin yarısını kurmuş olabilir. Söküm, bilinen tek tutarlı duruma —
hiçbir şey — dönmektir.

Bu yüzden varsayılan katıdır ve bilinçlidir. Yumuşatmanın yolu, çalışma zamanına hangi alt
ağacın başarısız olabileceğini ve o alt ağaç yerine ne gösterileceğini önceden söylemekten
geçer.

#### Hata Sınırı

Alt ağacındaki oluşturma hatalarını yakalayıp yerine önceden belirlenmiş bir görünüm koyan
bileşene **hata sınırı (error boundary)** denir. Koyduğu görünüme bu derste **yedek görünüm
(fallback)** denecektir; Tarayıcı ve Web Platformu kursundaki yuvaların yedek içeriğiyle
karıştırılmaması için ayrı bir ad kullanılmıştır.

Yayılma kuralı, Programlama Temelleri kursundaki istisna yayılımının aynısıdır: hata çağrı
zincirinde yukarı çıkar ve onu yakalayan **en yakın** sınırda durur.

```js
// hata-siniri.mjs — olusturma hatasinin en yakin sinira kadar yayilmasi
const agac = (sinirlar, bozuk) => ({
  ad: "sayfa", sinir: sinirlar.includes("sayfa"), cocuklar: [
    { ad: "baslik", cocuklar: [] },
    { ad: "suzgec-paneli", sinir: sinirlar.includes("suzgec-paneli"), cocuklar: [
      { ad: "suzgec-listesi", cocuklar: [] },
    ] },
    { ad: "olcum-tablosu", sinir: sinirlar.includes("olcum-tablosu"), cocuklar: [
      { ad: "satir-1", cocuklar: [] },
      { ad: "satir-2", hata: bozuk ? "birim alani eksik" : null, cocuklar: [] },
      { ad: "satir-3", cocuklar: [] },
    ] },
  ],
});

// Donen sayi, ekranda kalan bilesen sayisidir: yakalanan alt agacin isi atilir.
function olustur(d, kutuk) {
  if (d.sinir) {
    try {
      const p = d.cocuklar.map((c) => olustur(c, kutuk));
      return { metin: `${d.ad}(${p.map((x) => x.metin).join(" ")})`,
               sayi: 1 + p.reduce((t, x) => t + x.sayi, 0) };
    } catch (h) {
      kutuk.push(`${d.ad} yakaladi -> ${h.message}`);
      return { metin: `${d.ad}(YEDEK)`, sayi: 1 };
    }
  }
  if (d.hata) throw new Error(`${d.ad}: ${d.hata}`);
  if (d.cocuklar.length === 0) return { metin: d.ad, sayi: 1 };
  const p = d.cocuklar.map((c) => olustur(c, kutuk));
  return { metin: `${d.ad}(${p.map((x) => x.metin).join(" ")})`,
           sayi: 1 + p.reduce((t, x) => t + x.sayi, 0) };
}

const say = (d) => 1 + d.cocuklar.reduce((t, c) => t + say(c), 0);
const TOPLAM = say(agac([], false));

const HEPSI = ["sayfa", "suzgec-paneli", "olcum-tablosu"];
const SENARYOLAR = [
  ["uc sinir, bozuk kayit", HEPSI, true],
  ["yalniz sayfa siniri, bozuk kayit", ["sayfa"], true],
  ["hic sinir yok, bozuk kayit", [], true],
  ["uc sinir, kayit duzeltilmis", HEPSI, false],
];

for (const [baslik, sinirlar, bozuk] of SENARYOLAR) {
  const kutuk = [];
  console.log(baslik + ":");
  let sonuc;
  try {
    sonuc = olustur(agac(sinirlar, bozuk), kutuk);
  } catch (h) {
    kutuk.push(`kimse yakalamadi -> ${h.message}`);
    sonuc = { metin: "(agac tumuyle sokuldu)", sayi: 0 };
  }
  for (const s of kutuk) console.log(`  ${s}`);
  console.log(`  ekranda: ${sonuc.metin}`);
  console.log(`  ekranda kalan bilesen: ${sonuc.sayi}/${TOPLAM}`);
}
```

```
uc sinir, bozuk kayit:
  olcum-tablosu yakaladi -> satir-2: birim alani eksik
  ekranda: sayfa(baslik suzgec-paneli(suzgec-listesi) olcum-tablosu(YEDEK))
  ekranda kalan bilesen: 5/8
yalniz sayfa siniri, bozuk kayit:
  sayfa yakaladi -> satir-2: birim alani eksik
  ekranda: sayfa(YEDEK)
  ekranda kalan bilesen: 1/8
hic sinir yok, bozuk kayit:
  kimse yakalamadi -> satir-2: birim alani eksik
  ekranda: (agac tumuyle sokuldu)
  ekranda kalan bilesen: 0/8
uc sinir, kayit duzeltilmis:
  ekranda: sayfa(baslik suzgec-paneli(suzgec-listesi) olcum-tablosu(satir-1 satir-2 satir-3))
  ekranda kalan bilesen: 8/8
```

Aynı hata, sınır yerleşimine göre üç ayrı sonuç üretir.

Tablonun kendi sınırı varsa yalnız tablo yedek görünüme düşer; başlık ve süzgeç paneli
ayakta kalır ve **kullanılabilir durumdadır**. Kullanıcı süzgeci değiştirip başka bir
sorguyla yeniden deneyebilir.

Yalnız sayfa düzeyinde sınır varsa hata iki düzey yukarı çıkar ve bütün sayfa tek bir yedek
görünüme iner. Hiç sınır yoksa ekranda hiçbir şey kalmaz.

Son senaryo bir noktayı vurgular: sınır, hata olmadığında hiçbir şey yapmaz. Görünmez bir
maliyeti yoktur, yalnız hata anında devreye girer.

#### Sınırların Yerleştirilmesi

Çıktıdaki 5, 1 ve 0 sayıları yerleştirme kuralını verir: **sınır, çökebilecek parçanın
hemen üstüne konur, sayfanın en tepesine değil.**

Uygulamada üç düzeyli bir yerleşim yeterlidir. Sayfanın en dışında bir son çare sınırı
bulunur; hiçbir şey yakalamasa da beyaz ekran yerine bir ileti gösterir. Sayfanın her ana
bölümü — süzgeç paneli, ölçüm tablosu, karşılaştırma panosu — kendi sınırını taşır; biri
çökerken diğerleri çalışır. Dış veriye dayanan ve bağımsız yenilenebilen küçük parçalar,
örneğin tek bir ölçüm rozeti, kendi sınırını taşıyabilir.

Ters uç da bir hatadır. Her bileşeni sınırla sarmalamak, çöken parçanın yerine sessizce
küçük bir yedek koyar ve sayfa "çalışıyor" görünürken yarısı eksik olur. Sınır bir hata
gizleme aracı değil, hasarı sınırlama aracıdır.

#### Sınırın Yakalayamadıkları

Sınır bir çağrı yığını mekanizmasıdır ve kapsamı bu yığınla sınırlıdır.

```js
// yakalanamayan.mjs — sinirin kapsami cagri yiginiyla sinirlidir
const yigin = [];
const yiginDurumu = () => `yigin = [${yigin.join(" > ") || "bos"}]`;

function sinir(ad, govde) {
  yigin.push(ad);
  try {
    return govde();
  } catch (h) {
    console.log(`  ${ad} YAKALADI -> ${h.message}`);
    return `${ad}(YEDEK)`;
  } finally {
    yigin.pop();
  }
}

const bekleyenler = [];
let isleyici = null;

console.log("A. olusturma sirasinda atilan hata");
const cikti = sinir("tablo-siniri", () => {
  console.log(`  govde calisiyor, ${yiginDurumu()}`);
  isleyici = () => { throw new Error("silme dugmesi isleyicisi patladi"); };
  bekleyenler.push(["olcum yaniti", () => { throw new Error("ag yaniti bozuk") }]);
  throw new Error("satir-2: birim alani eksik");
});
console.log(`  ekranda: ${cikti}`);

console.log("B. olay isleyicisi: olusturma coktan bitti");
console.log(`  ${yiginDurumu()}`);
try { isleyici(); } catch (h) { console.log(`  sinira ugramadi -> ${h.message}`); }

console.log("C. eszamansiz geri cagirim: ayri bir tur");
for (const [ad, is] of bekleyenler) {
  console.log(`  ${ad} calisiyor, ${yiginDurumu()}`);
  try { is(); } catch (h) { console.log(`  sinira ugramadi -> ${h.message}`); }
}
```

```
A. olusturma sirasinda atilan hata
  govde calisiyor, yigin = [tablo-siniri]
  tablo-siniri YAKALADI -> satir-2: birim alani eksik
  ekranda: tablo-siniri(YEDEK)
B. olay isleyicisi: olusturma coktan bitti
  yigin = [bos]
  sinira ugramadi -> silme dugmesi isleyicisi patladi
C. eszamansiz geri cagirim: ayri bir tur
  olcum yaniti calisiyor, yigin = [bos]
  sinira ugramadi -> ag yaniti bozuk
```

Üç bölümdeki yığın satırları farkı tek başına açıklar. Gövde çalışırken sınır yığındadır ve
atılan hata ona uğrar. Olay işleyicisi çağrıldığında ve eşzamansız geri çağırım çalıştığında
yığın boştur; sınırın koruma bloğu çoktan sonlanmıştır.

Bunun pratik sonucu, hata işlemenin iki ayrı yerde kurulmasıdır. Oluşturma hataları sınırla
karşılanır. Olay işleyicilerindeki ve eşzamansız işlerdeki hatalar, o işi yapan kodun kendi
içinde yakalanır ve sonucu duruma yazılır — bir hata iletisi durumu, bir yeniden deneme
düğmesi. Aynı ayrım Eşzamansız JavaScript ve Çalışma Zamanı kursundaki yakalanmamış söz
reddi için de geçerlidir.

Sınırın kapsamadığı iki durum daha vardır: sınırın kendi yedek görünümü hata atarsa hata bir
üst sınıra çıkar ve sunucu tarafında üretilen bir oluşturmanın hataları istemcideki sınıra
uğramaz.

#### Kurtarma ve Bildirme

Yakalamak yetmez; yakalanan hatadan çıkış da tasarlanır.

Sınırın kendisi hata atan bileşenin **üstünde** olduğu için kendi durumu bozulmamıştır. Bu,
kurtarmayı olanaklı kılar. En yaygın biçim bir yeniden deneme eylemidir: sınır, yedek
görünümde bir düğme gösterir; düğme alt ağacın kimliğini değiştirerek onu baştan kurar.
Kimliğin değişmesi önemlidir — aynı kimlikle yeniden çizmek, hataya yol açan durumu
koruyup aynı hatayı üretir.

İkinci biçim yönlendirmedir: bozuk bir kayıt yüzünden ayrıntı görünümü çöküyorsa liste
görünümüne dönmek, kullanıcıyı çalışan bir ekranda bırakır.

Bildirme iki ayrı okuyucuya yapılır. Geliştiriciye kütüğe yazılan kayıt gider: hata iletisi,
yığıt izi, hangi bileşende oluştuğu, o anki yönlendirme adresi. Kullanıcıya ise ne yığıt izi
ne hata kodu gösterilir; ne olduğunu ve ne yapabileceğini söyleyen bir cümle yeterlidir.
Yedek görünümün de erişilebilir olması gerekir: iletinin okuyucuya duyurulması ve odağın
yeniden deneme düğmesine taşınması, Web'in Temelleri ve HTML kursundaki odak yönetimi
kurallarına uyar.

#### Özet

- Oluşturma ortasında atılan bir hata yarım bir ağaç bırakır; kısmi ağacı yazmak tutarsız
  bir ekran üretir, bu yüzden varsayılan davranış ağacı sökmektir.
- Hata sınırı, alt ağacındaki oluşturma hatalarını yakalayıp yerine yedek görünüm koyan
  bileşendir; hata en yakın sınırda durur.
- Sınır yerleşimi ekranda kalan bileşen sayısını belirler: bölüm düzeyinde sınır sayfanın
  geri kalanını kullanılabilir bırakır, yalnız sayfa düzeyinde sınır bırakmaz.
- Sınır bir çağrı yığını mekanizmasıdır; olay işleyicilerinde, zamanlayıcılarda ve
  eşzamansız geri çağırımlarda atılan hatalar kapsamı dışındadır ve orada yakalanır.
- Kurtarma, alt ağacın kimliğini değiştirerek yeniden kurmaktır; aynı kimlikle yeniden
  oluşturmak aynı hatayı üretir.
- Kütüğe ayrıntı, kullanıcıya tek cümle ve bir eylem gider; yedek görünüm de erişilebilir
  olmalıdır.

#### Sonraki Adım

Hata sınırı, bir alt ağacın **başarısız olduğu** durumu ağacın geri kalanından ayırır.
Aynı biçimde ayrılması gereken ikinci bir durum vardır: alt ağacın henüz **hazır olmadığı**
durum. Ölçüm tablosu, veriler sunucudan gelene kadar çizilemez; karşılaştırma panosu ikinci
istasyonun kayıtlarını bekler. Bunu her bileşende ayrı bir "yükleniyor" bayrağıyla yönetmek,
birbiriyle çelişebilen durum bileşimleri üretir ve iç içe beklemelerde her düzeyin ayrı bir
gösterge çevirmesine yol açar. Sonraki ders, hata sınırıyla aynı yukarı doğru sinyal
mekanizmasının bekleme için nasıl kullanıldığını ve yükleniyor görünümlerinin nerede
toplanacağını ele alır.

### Askıya Alma ve Yükleniyor Durumları

Source: https://academia.sh/tr/kurslar/bilesen-tabanli-gelistirme/askiya-alma-ve-yukleniyor-durumlari

Verisi hazır olmayan bileşenin oluşturmayı kesip en yakın bekleme sınırına sinyal vermesi; elle tutulan bayrakların ürettiği geçersiz bileşimler, sınır yerleşiminin açığa çıkardığı istek şelalesi, gösterge titremesinin ölçüsü ve geçişlerde eski içeriğin korunması.

Önceki ders bir alt ağacın **başarısız olduğu** durumu ağacın geri kalanından ayırdı.
Ayrılması gereken ikinci bir durum daha vardır: alt ağacın henüz **hazır olmadığı** durum.
Ölçüm tablosu, veriler sunucudan gelene kadar çizilemez; karşılaştırma panosu ikinci
istasyonun kayıtlarını bekler.

İki durum arasındaki benzerlik yapısaldır. Her ikisinde de bir bileşen kendi başına
çözemediği bir sorunla karşılaşır ve bunu yukarıya bildirir; yukarıdaki bir sınır bildirimi
yakalar ve alt ağacın yerine başka bir görünüm koyar. Bu derste ikinci mekanizmaya **askıya
alma (suspension)**, onu yakalayan bileşene **bekleme sınırı** denecektir.

#### Elle Tutulan Bayrakların Sorunu

Yaygın çözüm her bileşende üç ayrı durum tutmaktır: veri, yükleniyor bayrağı ve hata.
Üç ayrı yuva sekiz bileşim üretir ve bunların yalnız üçü anlamlıdır — yükleniyor, hata,
veri. Kalan beş bileşim geçersizdir: hem yükleniyor hem hatalı, hem veri var hem
yükleniyor, üçü birden boş.

Geçersiz bileşimler kod içinde ortaya çıkmaz, kenar durumlarda çıkar: ikinci istek
başlatılırken hata bayrağı sıfırlanmazsa ekranda hem gösterge hem hata iletisi görünür.
Bunu önlemenin yolu üç bayrağı tek bir ayrık durumla değiştirmektir; ama o zaman da her
bileşen kendi ayrık durumunu, kendi göstergesini ve kendi hata görünümünü taşır. Sayfada
altı veri okuyan bileşen varsa altı ayrı gösterge döner.

Askıya alma bu iki sorunu birden çözer: bileşen "hazır değilim" demenin dışında hiçbir durum
tutmaz; gösterge kararı yukarıya toplanır.

#### Askıya Alma

Bileşen, verisini okumaya çalışır. Veri hazırsa değeri alır ve oluşturmayı sürdürür. Hazır
değilse isteği başlatır ve oluşturmayı keser. Kesme yukarı çıkar, en yakın bekleme sınırında
durur, o sınır alt ağacın yerine bekleme görünümünü koyar. Veri geldiğinde alt ağaç yeniden
oluşturulur ve bu kez okuma başarılı olur.

```js
// askiya-alma.mjs — hazir olmayan bilesenin en yakin bekleme sinirina sinyal vermesi
const BEKLEME = Symbol("bekleme");

function depo() {
  const kayit = new Map();
  const baslangic = new Map();     // istegin hangi turda baslatildigi
  let tur = 0;
  return {
    turAc: () => { tur++; },
    oku(anahtar) {
      const k = kayit.get(anahtar);
      if (!k) {
        kayit.set(anahtar, { hal: "bekliyor" });
        baslangic.set(anahtar, tur);      // istek ilk okuma denemesinde baslar
        throw BEKLEME;
      }
      if (k.hal === "bekliyor") throw BEKLEME;
      return k.veri;
    },
    yanitla(anahtar, veri) { kayit.set(anahtar, { hal: "hazir", veri }); },
    ozet: () => [...baslangic].map(([a, t]) => `${a}: ${t}. turda`).join(", "),
  };
}

const dugum = (ad, s = {}) => ({ ad, sinir: !!s.sinir, veri: s.veri || null, cocuklar: s.cocuklar || [] });

function olustur(d, kaynak) {
  if (d.sinir) {
    try {
      const p = d.cocuklar.map((c) => olustur(c, kaynak));
      return { metin: `${d.ad}[${p.map((x) => x.metin).join(" ")}]`,
               sayi: 1 + p.reduce((t, x) => t + x.sayi, 0) };
    } catch (h) {
      if (h !== BEKLEME) throw h;
      return { metin: `${d.ad}[YUKLENIYOR]`, sayi: 1 };
    }
  }
  const etiket = d.veri ? `${d.ad}<${kaynak.oku(d.veri)}>` : d.ad;
  if (d.cocuklar.length === 0) return { metin: etiket, sayi: 1 };
  const p = d.cocuklar.map((c) => olustur(c, kaynak));
  return { metin: `${etiket}(${p.map((x) => x.metin).join(" ")})`,
           sayi: 1 + p.reduce((t, x) => t + x.sayi, 0) };
}

const ayriSinirlar = () => dugum("sayfa", { cocuklar: [
  dugum("baslik"),
  dugum("tablo-siniri", { sinir: true, cocuklar: [dugum("olcum-tablosu", { veri: "olcumler" })] }),
  dugum("pano-siniri", { sinir: true, cocuklar: [dugum("karsilastirma-panosu", { veri: "karsilastirma" })] }),
] });

const tekSinir = () => dugum("sayfa", { cocuklar: [
  dugum("baslik"),
  dugum("ortak-sinir", { sinir: true, cocuklar: [
    dugum("olcum-tablosu", { veri: "olcumler" }),
    dugum("karsilastirma-panosu", { veri: "karsilastirma" }),
  ] }),
] });

for (const [ad, kur] of [["iki ayri bekleme siniri", ayriSinirlar], ["tek ortak bekleme siniri", tekSinir]]) {
  console.log(ad + ":");
  const kaynak = depo();
  const turlar = [
    ["ilk olusturma", null],
    ["olcumler geldi", "olcumler"],
    ["karsilastirma geldi", "karsilastirma"],
  ];
  for (const [baslik, gelen] of turlar) {
    kaynak.turAc();
    if (gelen) kaynak.yanitla(gelen, `${gelen} verisi`);
    const s = olustur(kur(), kaynak);
    console.log(`  ${baslik.padEnd(20)} icerik gosteren bilesen: ${s.sayi}`);
    console.log(`    ${s.metin}`);
  }
  console.log(`  istek baslangici -> ${kaynak.ozet()}`);
}
```

```
iki ayri bekleme siniri:
  ilk olusturma        icerik gosteren bilesen: 4
    sayfa(baslik tablo-siniri[YUKLENIYOR] pano-siniri[YUKLENIYOR])
  olcumler geldi       icerik gosteren bilesen: 5
    sayfa(baslik tablo-siniri[olcum-tablosu<olcumler verisi>] pano-siniri[YUKLENIYOR])
  karsilastirma geldi  icerik gosteren bilesen: 6
    sayfa(baslik tablo-siniri[olcum-tablosu<olcumler verisi>] pano-siniri[karsilastirma-panosu<karsilastirma verisi>])
  istek baslangici -> olcumler: 1. turda, karsilastirma: 1. turda
tek ortak bekleme siniri:
  ilk olusturma        icerik gosteren bilesen: 3
    sayfa(baslik ortak-sinir[YUKLENIYOR])
  olcumler geldi       icerik gosteren bilesen: 3
    sayfa(baslik ortak-sinir[YUKLENIYOR])
  karsilastirma geldi  icerik gosteren bilesen: 5
    sayfa(baslik ortak-sinir[olcum-tablosu<olcumler verisi> karsilastirma-panosu<karsilastirma verisi>])
  istek baslangici -> olcumler: 1. turda, karsilastirma: 2. turda
```

Sayfa başlığı her iki yerleşimde de ilk turda görünür: beklemek yalnız veri okuyan alt ağacı
durdurur, kardeşlerini değil. Bu, hata sınırındaki yalıtımın aynısıdır.

#### Sınır Yerleşimi ve İstek Şelalesi

İki yerleşim arasındaki fark ikinci turda ortaya çıkar. Ayrı sınırlarda ölçüm tablosu
verisi gelir gelmez görünür; ortak sınırda ekran hiç değişmez, çünkü sınırın altındaki
bütün ağaç hazır olmadıkça yedek görünüm kalkmaz. Bekleme sınırı, "birlikte görünmesi
gereken" parçaları tanımlar.

Çıktının son satırı daha sinsi bir farkı gösterir. Ayrı sınırlarda iki istek de birinci
turda başlamıştır. Ortak sınırda ölçüm tablosunun kesmesi kardeşinin oluşturulmasını
engellediği için karşılaştırma isteği ancak ikinci turda başlar. İkinci istek, birincinin
yanıtını beklemiş olur; iki bağımsız istek ardışıklaşır. Buna **istek şelalesi (request
waterfall)** denir — Tarayıcı ve Web Platformu kursundaki ağ panelinde bu, basamaklı bir
şelale görünümü olarak okunur.

Şelalenin ikinci ve daha yaygın biçimi iç içe bileşenlerde ortaya çıkar: üst bileşen
istasyon kaydını okur; alt bileşen o kayıttaki kimlikle ölçümleri okur. İkinci istek
gerçekten birinciye bağlıdır ve yerleşimle çözülmez. Çözüm, isteği oluşturmanın başlatmasını
beklememektir: veri isteği yönlendirme çözümlendiği anda başlatılır, bileşen oluşturulduğunda
istek çoktan yoldadır. Kural şudur: **veri gereksinimi, oluşturmadan önce bilinebiliyorsa
istek oluşturmadan önce başlatılır.**

#### Gösterge Titremesi

Bekleme görünümünün ne zaman gösterileceği ayrı bir karardır ve ölçülebilir.

```js
// gosterge.mjs — yukleniyor gostergesinin ne zaman cikacagi ve ne kadar kalacagi
const YANITLAR = [80, 150, 220, 900];   // ms cinsinden yanit sureleri
const ESIK = 200;    // bu sureden once gelen yanitta gosterge hic cikmaz
const EN_AZ = 400;   // gosterge ciktiysa en az bu kadar ekranda kalir

function hesapla(politika, yanit) {
  if (politika === "hemen") return { baslar: 0, biter: yanit, icerik: yanit };
  if (yanit <= ESIK) return { baslar: null, biter: null, icerik: yanit };
  if (politika === "gecikmeli") return { baslar: ESIK, biter: yanit, icerik: yanit };
  const biter = Math.max(yanit, ESIK + EN_AZ);      // gecikmeli + en az sure
  return { baslar: ESIK, biter, icerik: biter };
}

for (const politika of ["hemen", "gecikmeli", "gecikmeli+en az"]) {
  console.log(`${politika}:`);
  for (const yanit of YANITLAR) {
    const s = hesapla(politika, yanit);
    const sure = s.baslar === null ? 0 : s.biter - s.baslar;
    const satir = [
      `  yanit ${String(yanit).padStart(3)} ms ->`,
      `gosterge ${s.baslar === null ? "cikmaz" : `${s.baslar}-${s.biter} ms`}`.padEnd(20),
      `gorunme suresi ${String(sure).padStart(3)} ms`,
      `| icerik ${String(s.icerik).padStart(3)} ms'de`,
    ].join(" ");
    console.log(sure > 0 && sure < 300 ? `${satir}  <- titreme` : satir);
  }
}
```

```
hemen:
  yanit  80 ms -> gosterge 0-80 ms     gorunme suresi  80 ms | icerik  80 ms'de  <- titreme
  yanit 150 ms -> gosterge 0-150 ms    gorunme suresi 150 ms | icerik 150 ms'de  <- titreme
  yanit 220 ms -> gosterge 0-220 ms    gorunme suresi 220 ms | icerik 220 ms'de  <- titreme
  yanit 900 ms -> gosterge 0-900 ms    gorunme suresi 900 ms | icerik 900 ms'de
gecikmeli:
  yanit  80 ms -> gosterge cikmaz      gorunme suresi   0 ms | icerik  80 ms'de
  yanit 150 ms -> gosterge cikmaz      gorunme suresi   0 ms | icerik 150 ms'de
  yanit 220 ms -> gosterge 200-220 ms  gorunme suresi  20 ms | icerik 220 ms'de  <- titreme
  yanit 900 ms -> gosterge 200-900 ms  gorunme suresi 700 ms | icerik 900 ms'de
gecikmeli+en az:
  yanit  80 ms -> gosterge cikmaz      gorunme suresi   0 ms | icerik  80 ms'de
  yanit 150 ms -> gosterge cikmaz      gorunme suresi   0 ms | icerik 150 ms'de
  yanit 220 ms -> gosterge 200-600 ms  gorunme suresi 400 ms | icerik 600 ms'de
  yanit 900 ms -> gosterge 200-900 ms  gorunme suresi 700 ms | icerik 900 ms'de
```

Göstergeyi hemen göstermek, hızlı yanıtlarda ekranda beliren ve hemen kaybolan bir kare
üretir. Kullanıcı bunu bir yükleme olarak değil bir sıçrama olarak algılar; iki durum
değişimi, tek bir bekleme görünmesinden daha rahatsız edicidir.

Gecikmeli gösterim eşiğin altındaki yanıtlarda göstergeyi hiç çıkarmaz. Ne var ki eşiğin
hemen üstündeki yanıtta yirmi milisaniyelik bir titreme bırakır — sorunun en kötü biçimi.

Üçüncü politika bunu en az gösterim süresiyle kapatır ve bedelini görünür kılar: iki yüz
yirmi milisaniyede hazır olan içerik altı yüz milisaniyeye kadar bekletilir. Bu, bilinçli
olarak ödenen bir gecikmedir; hızlı yanıtın ödülünü kullanıcıya vermez, karşılığında
sarsıntısız bir ekran verir. Üç politika arasındaki seçim ölçülen yanıt dağılımına bağlıdır:
yanıtların çoğu eşiğin altındaysa ikinci, eşiğin çevresinde toplanıyorsa üçüncü politika
seçilir.

#### Bekleme Görünümünün Tasarımı ve Geçişler

Bekleme görünümünün geleceği içeriğin yerini tutması gerekir. Dönen bir gösterge, yerini
alacağı tablodan küçükse veri geldiğinde sayfa aşağı kayar; Web'in Temelleri ve HTML
kursunda tanımlanan **içerik kaymasıdır** bu. Bunun yerine gelecek düzenin ölçülerini
taşıyan bir iskelet görünüm konur: aynı satır sayısı, aynı sütun genişlikleri, aynı yükseklik.

İkinci karar geçişlerdedir. İlk yükleme ile sonraki yükleme farklı sorunlardır. Kullanıcı
süzgeci değiştirdiğinde ekranda çalışan bir tablo vardır; yedek görünüme düşmek bu tabloyu
silip iskelete dönmek olur. Çalışan bir ekranı boşaltmak, kullanıcının bağlamını da siler.

Daha iyi davranış, eski içeriği yerinde tutup bir bekleme göstergesi eklemektir — solmuş
bir tablo, başlıkta küçük bir gösterge, devre dışı bırakılmış bir süzgeç. Kural şöyle
yazılabilir: **bekleme sınırı boş bir alanı doldurmak içindir; dolu bir alanı boşaltmak
için değil.** Eski içerik gösterilirken onun eski olduğunun belirtilmesi gerekir, yoksa
kullanıcı güncellenmiş veri gördüğünü sanır.

Erişilebilirlik tarafı da atlanmaz. Bekleme ve tamamlanma, ekran okuyucuya duyurulmalıdır;
görsel bir göstergenin tek başına anlamı yoktur. Devre dışı bırakılan denetimlerin nedeni de
metinle bildirilir.

#### Özet

- Her bileşende ayrı tutulan veri, yükleniyor ve hata bayrakları sekiz bileşim üretir ve
  bunların beşi geçersizdir.
- Askıya alma, verisi hazır olmayan bileşenin oluşturmayı kesip yukarıya sinyal vermesidir;
  en yakın bekleme sınırı sinyali yakalar, alt ağacın yerine bekleme görünümü koyar ve
  kardeş alt ağaçlar oluşturulmayı sürdürür.
- Bekleme sınırı, birlikte görünmesi gereken parçaları tanımlar; tek bir sınırın altında
  toplanan bağımsız istekler ardışıklaşarak istek şelalesi üretir.
- Veri gereksinimi oluşturmadan önce bilinebiliyorsa istek oluşturmadan önce başlatılır.
- Göstergeyi hemen göstermek titreme üretir; eşikli gösterim kısa yanıtlarda göstergeyi
  eler, en az gösterim süresi titremeyi kapatır ama içeriği geciktirir.
- Bekleme görünümü gelecek içeriğin ölçülerini taşımalıdır; süzgeç değişimi gibi
  geçişlerde çalışan içerik silinmez, eski olduğu belirtilerek korunur.

#### Sonraki Adım

Buraya kadarki yedi ders bir alt ağacın **ne zaman** oluşturulacağını belirledi: durum
değiştiğinde, bağımlılık değiştiğinde, hata yakalandığında, veri geldiğinde. Bir soru daha
kaldı ve o zamanla değil yerle ilgili: alt ağaç **nereye** çizilir?

Varsayılan yanıt açıktır — bileşen ağacındaki yeri, belge ağacındaki yerini belirler. Ölçüm
istasyonu sayfasında bu varsayılanın kırıldığı bir nokta vardır. Süzgeç panelindeki açılır
liste, panelin kendi kutusundan taşar; kabın kırpması listeyi keser; kabın yığılma bağlamı
listeyi tablonun altında bırakır. Bileşen mantıksal olarak panelin içindedir ama görsel
olarak sayfanın en üstünde çizilmelidir. Sonraki ders bu ayrılığı, iki ağacın birbirinden
bağımsız hale gelmesini ve bunun olay yayılımıyla erişilebilirliğe ne yaptığını ele alır.

### Taşıma

Source: https://academia.sh/tr/kurslar/bilesen-tabanli-gelistirme/tasima

Bir alt ağacın bileşen ağacındaki yerini koruyup belge ağacında başka bir düğüme çizilmesi; kırpma ve yığılma kısıtları, iki ağacın ayrılması, olay yolunun ve bağlam çözümlemesinin bileşen ağacını izlemesi, hedef düğümün koşulları ve erişilebilirlik yükümlülükleri.

Önceki yedi ders bir alt ağacın **ne zaman** oluşturulacağını belirledi: durum
değiştiğinde, bağımlılık değiştiğinde, hata yakalandığında, veri geldiğinde. Bir soru daha
kaldı ve o zamanla değil yerle ilgilidir: alt ağaç **nereye** çizilir?

Varsayılan yanıt açıktır. Bileşen ağacındaki yer, belge ağacındaki yeri belirler; bir
bileşenin ürettiği düğümler, üst bileşeninin düğümlerinin içine girer. Ölçüm istasyonu
sayfasında bu varsayılanın işe yaramadığı bir nokta vardır: süzgeç panelindeki açılır liste.
Liste mantıksal olarak panelin bir parçasıdır — panelin durumunu okur, panelin olaylarını
üretir — ama görsel olarak panelin kutusunun dışına çıkması gerekir.

Bu derste kurulan mekanizmaya **taşıma (portal)** denir: bileşen ağacındaki yeri koruyup
belge ağacındaki yeri değiştirmek.

#### İki Kısıt: Kırpma ve Yığılma

Sorun bir tercih değil, iki ölçülebilir kısıttır.

```js
// kirpma-ve-yigilma.mjs — tasimanin cozdugu iki gorsel kisit
const kes = (a, b) => ({ ust: Math.max(a.ust, b.ust), alt: Math.min(a.alt, b.alt) });
const yukseklik = (r) => Math.max(0, r.alt - r.ust);

const acilirListe = { ust: 280, alt: 520 };            // 240 piksel yuksekliginde
const suzgecPaneli = { ust: 120, alt: 320 };           // tasan icerigi kirpiyor
const gorunumAlani = { ust: 0, alt: 800 };

console.log("kirpma:");
for (const [ad, kirpan] of [["panelin icinde", suzgecPaneli], ["tasinmis (govde koku)", gorunumAlani]]) {
  const g = kes(acilirListe, kirpan);
  console.log(
    `  ${ad.padEnd(22)} gorunen ${String(yukseklik(g)).padStart(3)}/${yukseklik(acilirListe)} piksel`,
    `(${g.ust}-${g.alt})`,
  );
}

// Yigilma zinciri: kokten baslayarak her yigilma baglaminin z-index degeri.
const zincirler = {
  "acilir-liste (panelin icinde)": [1, 9999],   // panel kendi yigilma baglamini kuruyor
  "acilir-liste (tasinmis)": [9999],
  "tablo basligi (yapiskan)": [5],
};

function karsilastir(a, b) {
  const n = Math.max(a.length, b.length);
  for (let i = 0; i < n; i++) {
    const x = i < a.length ? a[i] : -Infinity;
    const y = i < b.length ? b[i] : -Infinity;
    if (x !== y) return x < y ? "ALTTA" : "USTTE";
  }
  return "ESIT";
}

console.log("yigilma sirasi (tablo basligina gore):");
const basligin = zincirler["tablo basligi (yapiskan)"];
for (const ad of ["acilir-liste (panelin icinde)", "acilir-liste (tasinmis)"]) {
  const z = zincirler[ad];
  console.log(`  ${ad.padEnd(30)} zincir=[${z.join(", ")}] -> ${karsilastir(z, basligin)}`);
}
```

```
kirpma:
  panelin icinde         gorunen  40/240 piksel (280-320)
  tasinmis (govde koku)  gorunen 240/240 piksel (280-520)
yigilma sirasi (tablo basligina gore):
  acilir-liste (panelin icinde)  zincir=[1, 9999] -> ALTTA
  acilir-liste (tasinmis)        zincir=[9999] -> USTTE
```

Birinci kısıt kırpmadır. Süzgeç paneli kendi kutusundan taşan içeriği kesiyorsa, iki yüz
kırk piksellik listenin yalnız kırkı görünür. Bu bir biçem hatası değildir; panelin kendi
kaydırma davranışı için gereken kesme, listeyi de keser.

İkinci kısıt yığılmadır. CSS ile Görsel Sunum kursunda tanımlanan **yığılma bağlamı**, bir
ögenin sıra numarasını yalnız kendi bağlamı içinde anlamlı kılar. Panel kendi bağlamını
kuruyorsa, içindeki dokuz bin dokuz yüz doksan dokuz değeri panelin kök bağlamdaki birine
bağlıdır. Karşılaştırma zincirin ilk ögesinden yapılır: bir, beşten küçüktür, dolayısıyla
liste yapışkan tablo başlığının altında kalır. Sayıyı büyütmek işe yaramaz; büyütülen sayı
yanlış bağlamdadır.

Her iki kısıt da tek bir nedenden doğar: alt ağacın belge içindeki **konumu**. Konumu
değiştirmenin yolu ya bileşen ağacını bozmaktır — açılır listeyi paneldeki mantıktan koparıp
sayfa köküne taşımak — ya da taşımadır.

#### İki Ağacın Ayrılması

Taşıma, alt ağacın bileşen ağacındaki yerini olduğu gibi bırakır ve yalnız belge ağacındaki
hedefini değiştirir. Sonuç, o noktadan itibaren birbirinden ayrılan iki ağaçtır.

```js
// tasima.mjs — bilesen agaci ile belge agacinin ayrilmasi
// acilir-liste bilesen agacinda suzgec-panelinin cocugu, belge agacinda govde-kokun cocugu.
const BILESEN_UST = {
  "secenek-sicaklik": "acilir-liste",
  "acilir-liste": "suzgec-paneli",
  "suzgec-dugmesi": "suzgec-paneli",
  "suzgec-paneli": "sayfa",
  "baslik": "sayfa",
  "olcum-tablosu": "sayfa",
  "sayfa": null,
};

const BELGE_UST = {
  "secenek-sicaklik": "acilir-liste",
  "acilir-liste": "govde-kok",
  "govde-kok": "belge",
  "suzgec-dugmesi": "suzgec-paneli",
  "suzgec-paneli": "uygulama-koku",
  "baslik": "uygulama-koku",
  "olcum-tablosu": "uygulama-koku",
  "uygulama-koku": "belge",
  "belge": null,
};

const SAGLAYICILAR = { sayfa: "santigrat" };   // birim baglami sayfada saglaniyor
const VARSAYILAN_BIRIM = "kelvin";

const yol = (harita, dugum) => {
  const y = [];
  for (let d = dugum; d; d = harita[d]) y.push(d);
  return y;
};

const HEDEF = "secenek-sicaklik";
const bilesenYolu = yol(BILESEN_UST, HEDEF);
const belgeYolu = yol(BELGE_UST, HEDEF);

console.log(`hedef: ${HEDEF}`);
console.log(`  bilesen agaci yolu: ${bilesenYolu.join(" > ")}`);
console.log(`  belge agaci yolu  : ${belgeYolu.join(" > ")}`);

console.log("tiklama dinleyicileri:");
for (const dinleyici of ["suzgec-paneli", "olcum-tablosu", "uygulama-koku", "govde-kok"]) {
  const b = bilesenYolu.includes(dinleyici);
  const d = belgeYolu.includes(dinleyici);
  console.log(
    `  ${dinleyici.padEnd(15)} bilesen agacina gore: ${b ? "calisir " : "calismaz"}`,
    `| belge agacina gore: ${d ? "calisir" : "calismaz"}`,
  );
}

console.log("baglam cozumlemesi (birim):");
for (const [ad, y] of [["bilesen agaci", bilesenYolu], ["belge agaci", belgeYolu]]) {
  const bulunan = y.find((d) => d in SAGLAYICILAR);
  console.log(`  ${ad.padEnd(14)} -> ${bulunan ? SAGLAYICILAR[bulunan] : VARSAYILAN_BIRIM}`,
              `(${bulunan ? `saglayici: ${bulunan}` : "saglayici yok"})`);
}
```

```
hedef: secenek-sicaklik
  bilesen agaci yolu: secenek-sicaklik > acilir-liste > suzgec-paneli > sayfa
  belge agaci yolu  : secenek-sicaklik > acilir-liste > govde-kok > belge
tiklama dinleyicileri:
  suzgec-paneli   bilesen agacina gore: calisir  | belge agacina gore: calismaz
  olcum-tablosu   bilesen agacina gore: calismaz | belge agacina gore: calismaz
  uygulama-koku   bilesen agacina gore: calismaz | belge agacina gore: calismaz
  govde-kok       bilesen agacina gore: calismaz | belge agacina gore: calisir
baglam cozumlemesi (birim):
  bilesen agaci  -> santigrat (saglayici: sayfa)
  belge agaci    -> kelvin (saglayici yok)
```

Bağlam çözümlemesi ve olay yayılımı için hangi ağacın geçerli olduğu, taşımanın en çok
şaşırtan yanıdır. Yanıt tektir: **bileşen ağacı.**

Bir seçeneğe tıklandığında olay, belge ağacında yukarı çıkar — ki orada süzgeç paneli
yoktur — ama çalışma zamanı olayı bileşen ağacındaki yol boyunca yeniden yayar. Süzgeç
panelinde kurulmuş bir tıklama dinleyicisi çalışır. Bu, Tarayıcı ve Web Platformu kursundaki
gölge ağacın birleşik yolla yaptığı işin bir benzeridir: fiziksel yayılım bir ağaçta, mantıksal
yayılım başka bir ağaçtadır.

Aynı şey bağlam için de geçerlidir. Taşınan alt ağaç, belge ağacında sağlayıcının dışında
kalmış olsa da bileşen ağacındaki sağlayıcıyı görür ve santigratı okur. Belge ağacı üzerinden
çözümlense varsayılana düşerdi. Aynı kural önceki iki dersin mekanizmaları için de işler:
taşınan alt ağaçtaki bir oluşturma hatası, bileşen ağacındaki en yakın hata sınırına gider;
taşınan alt ağaçtaki bir askıya alma, bileşen ağacındaki en yakın bekleme sınırında yakalanır.

Bu tutarlılık taşımayı kullanılabilir kılan şeydir. Taşıma bir kaçış yolu değildir; yalnız
çizim hedefini değiştirir, mantıksal ilişkilerin hiçbirini bozmaz.

#### Hedef Düğümün Koşulları

Hedefin sağlaması gereken üç koşul vardır.

Hedef, alt ağaç yazılırken belgede **bulunmalıdır**. Uygulamanın kökünün yanına kardeş bir
kap koymak bu koşulu en basit biçimde karşılar. Hedefi program içinde üretip aynı anda
kullanmak, düğümün henüz belgeye eklenmemiş olması riskini taşır.

Hedefin ömrü, taşınan bileşenin ömründen **uzun olmalıdır**. Hedef, taşıyan bileşenin
kendisi tarafından üretiliyorsa, bileşen ağaçtan çıkarken hedefi de götürür ve söküm sırası
belirsizleşir.

Hedef, uygulamanın söküm sırasına **dahil olmalıdır**. Taşınan alt ağaç, kendi yan
etkilerinin temizlemesini normal biçimde çalıştırır; ama hedef kabın kendisi uygulama dışında
üretilmişse boş bir düğüm olarak belgede kalır. Tek bir paylaşılan hedef kullanmak bu
birikimi önler.

Sunucu tarafında oluşturma yapılıyorsa dördüncü bir koşul eklenir: sunucuda bir belge
bulunmadığı için taşınan içerik ilk yanıtta yer almaz, ilk yazma aşamasından sonra belirir.
İlk görünümde bulunması gereken içerik taşınmaz.

#### Erişilebilirlik

Taşıma, görsel ilişkiyi düzeltirken belgedeki ilişkiyi koparır ve bu kopukluk yardımcı
teknolojiler için gerçektir. Dört yükümlülük doğar.

**Okuma sırası.** Belge ağacında açılır liste artık sayfanın sonundadır; ekran okuyucu onu
tetikleyen düğmenin yanında değil, sonda bulur. Bağ, düğme ile listeyi ilişkilendiren
öznitelikler ve uygun ARIA rolleriyle yeniden kurulur.

**Odak yönetimi.** Liste açıldığında odak listeye taşınır, kapandığında **onu açan düğmeye
geri döner**. Odağın geri dönmemesi, kullanıcıyı belgenin sonunda bırakır. Web'in Temelleri
ve HTML kursundaki odak yönetimi kuralları burada değişmeden geçerlidir.

**Odak tuzağı.** Kalıcı bir katman söz konusuysa — bir onay penceresi — odak katmanın içinde
tutulur ve arkadaki içerik yardımcı teknolojilerden gizlenir. Görsel olarak örtülmüş bir
içerik, gizlenmediği sürece hâlâ okunabilir durumdadır.

**Kapatma yolu.** Sekme sırası dışına çıkmış bir katmanın klavyeyle kapatılabilmesi gerekir;
kaçış tuşu ve dışarı tıklama iki ayrı yoldur ve ikisi de kurulur.

#### Taşımanın Gerekmediği Durumlar

Taşıma bir maliyet taşır: iki ağacın ayrılması, hata ayıklamayı zorlaştırır ve yukarıdaki
dört yükümlülüğü doğurur. Gerekmediği yerde kullanılmaz.

Kırpma sorunu çoğu zaman düzenle çözülür. Panelin taşan içeriği kesmesi gerçekten gerekli
mi, yoksa bir kaydırma kabı yanlış yere mi konmuş? Yığılma sorunu da zincirin
düzeltilmesiyle çözülebilir: paneli gereksiz yere yığılma bağlamı kuran bir özellikten
kurtarmak, listeyi kök bağlama geri getirir.

Ölçüt tek cümleyle yazılabilir: **taşıma, alt ağacın belge içindeki konumu düzeltilemediğinde
kullanılır.** Konum düzeltilebiliyorsa taşımaya gerek yoktur.

#### Özet

- Taşıma, alt ağacın bileşen ağacındaki yerini koruyup belge ağacındaki hedefini değiştirir.
- Gerekçesi iki ölçülebilir kısıttır: üst kabın taşan içeriği kırpması ve üst kabın kurduğu
  yığılma bağlamının sıra numarasını kendi içine hapsetmesi.
- Olay yayılımı, bağlam çözümlemesi, hata sınırları ve bekleme sınırları bileşen ağacını
  izler; belge ağacındaki kopukluk bu ilişkileri bozmaz.
- Hedef düğüm yazma anında belgede bulunmalı, taşınan bileşenden uzun ömürlü olmalı ve
  uygulamanın söküm sırasına dahil olmalıdır.
- Taşıma belgedeki okuma sırasını bozar; ilişkilendirme öznitelikleri, odağın geri dönmesi,
  gerekliyse odak tuzağı ve klavyeyle kapatma yolu ayrıca kurulur.
- Kırpma ve yığılma düzenle çözülebiliyorsa taşıma kullanılmaz.

#### Sonraki Adım

Bu konu tek bir bileşenin iç yaşamını tamamladı. Bileşenin oluşturmalar arasında ne
sakladığını, dış dünyayla nasıl eşzamanlandığını, hangi değerleri hesapladığını, hangilerini
sessizce tuttuğunu, ağaçtan ne okuduğunu, hata ve bekleme durumlarında ne olduğunu ve
çıktısını nereye çizdiğini biliyoruz. Hepsi tek bir bileşenin sınırları içinde kaldı.

Ölçüm istasyonu sayfası ise tek bir bileşen değil; süzgeç paneli, ölçüm tablosu, ölçüm
rozeti ve karşılaştırma panosundan oluşan bir aile. Bu parçaların birbirine nasıl
bağlanacağı ayrı bir sorudur ve yanıtı yalnız "üstten alta özellik vermek" değildir. Bir
bileşen başka bir bileşeni içerik olarak alabilir, iki bileşen ortak bir sözleşmeyi
paylaşabilir, aynı davranış farklı görünümlerle yeniden kullanılabilir. Sonraki konunun ilk
dersi olan Bileşim Kalıpları, bileşenleri birleştirmenin bu yollarını ve her birinin
kapsülleme ile esneklik arasında kurduğu dengeyi ele alır.

## Bileşim ve Yeniden Kullanım

Sarmalama ile yuva tabanlı bileşimin karşılaştırması, mantığın görünümden ayrılması, birlikte çalışan bileşen aileleri, sunum–konteyner ayrımı, başsız bileşenler ve özellik yayılmasıyla varsayılanların tasarımı.

### Bileşim Kalıpları

Source: https://academia.sh/tr/kurslar/bilesen-tabanli-gelistirme/bilesim-kaliplari

Sarmalayan bileşen ile yuva tabanlı bileşimin karşılaştırması; seçenek patlaması, özellik zincirinin ölçülmesi, kalıtım yerine bileşim ve iki kalıbın hangi koşulda doğru olduğu.

Önceki konu tek bir bileşeni sonuna kadar götürdü: yerel durumu, yan etkilerinin
temizlenmesi, türetilmiş değerlerinin bellenmesi, hata yalıtımı ve ağacın dışına çizim.
Bileşen artık kendi başına eksiksiz bir birimdir. Ancak bir arayüz tek bileşenden oluşmaz.
Kuzey Yamaç Ölçüm İstasyonu sayfasında on beş ölçüm rozeti, bir ölçüm tablosu ve bir süzgeç
paneli var; üçü de aynı yapı taşlarından kuruluyor.

Bu ders, tek bileşen çözüldükten sonra kalan soruyu ele alır: iki bileşen bir araya
getirilirken hangisi diğerinin neyini bilecek? Yanıtın iki ayrı kalıbı vardır ve aralarındaki
seçim, bileşenin ömrü boyunca ne kadar değişeceğini belirler.

#### İki Kalıp

**Bileşim (composition)**, bir bileşenin başka bileşenlerden kurulmasıdır. (Terim M14/K03
Düzen Sistemleri kursunda çizim katmanlarının birleştirilmesi için de kullanılmıştı; burada
kastedilen bileşen ağacının kurulmasıdır.) İki kalıbı vardır.

**Sarmalama.** Bir bileşen, başka bir bileşeni içeride çağırır ve dışarıya kendi özellik
kümesini sunar. (Bileşen Kavramı dersinde tanımlanan girdi özelliği, bu konu boyunca kısaca
**özellik** diye anılır.) Çağıran, sarmalananı hiç görmez; yalnızca sarmalayanın
özelliklerini doldurur. Yapı bileşenin içinde sabittir.

**Yuva tabanlı bileşim.** Bileşen, iç yapısında bir delik bırakır ve o deliğe konacak ağacı
dışarıdan alır. Ne konacağını çağıran belirler; bileşen yalnızca nereye konacağını bilir.
Web Bileşenleri konusundaki Şablonlar ve Yuvalar dersinde bu, tarayıcının kendi yuva atama
algoritmasıyla gösterilmişti; çerçeve düzeyinde aynı fikir bileşenin bir özelliği olarak
geçer: değeri metin ya da sayı değil, bir ağaç parçasıdır.

İkisi aynı çıktıyı üretebilir. Ayrıştıkları yer çıktı değil, değişim karşısındaki davranıştır.

#### Seçenek Patlaması

Ölçüm rozetiyle başlayalım. İlk sürümde rozet başlığı, değeri ve birimi çiziyor. Sonra
eşik aşımını göstermesi isteniyor; sonra kaynağın adını, sonra eğilim okunu, sonra bir
yenileme düğmesini, sonra bir not satırını.

Sarmalayan kalıpta her yeni istek bir mantıksal özellik ve bir dallanma ekler.

```js
// bilesim.mjs — sarmalayan bilesen ile yuva tabanli bilesimin karsilastirmasi
const oge = (ad, ozn = {}, ...cocuklar) => ({ ad, ozn, cocuklar: cocuklar.flat().filter(Boolean) });
const metin = (deger) => ({ ad: "#metin", ozn: {}, cocuklar: [], metin: deger });

// Ortak cekirdek: basligi, degeri ve birimi cizen rozet.
const cekirdek = ({ baslik, deger, birim }, ...ek) =>
  oge("div", { class: "rozet" },
    oge("span", { class: "rozet-baslik" }, metin(baslik)),
    oge("output", { class: "rozet-deger" }, metin(`${deger} ${birim}`)),
    ...ek);

// (a) Sarmalayan bilesen: her yeni ihtiyac bir mantiksal ozellik ve bir dallanma ekler.
function rozetSarmalayanV1({ baslik, deger, birim, esikAsildi, kaynakGoster, kaynak }) {
  const ek = [];
  if (esikAsildi) ek.push(oge("span", { class: "rozet-uyari" }, metin("esik asildi")));
  if (kaynakGoster) ek.push(oge("small", { class: "rozet-kaynak" }, metin(kaynak)));
  return cekirdek({ baslik, deger, birim }, ...ek);
}

// Altinci ihtiyac geldiginde ayni govde yeniden acilir.
function rozetSarmalayanV2({ baslik, deger, birim, esikAsildi, kaynakGoster, kaynak,
                             egilim, yenileDugmesi, notMetni }) {
  const ek = [];
  if (esikAsildi) ek.push(oge("span", { class: "rozet-uyari" }, metin("esik asildi")));
  if (kaynakGoster) ek.push(oge("small", { class: "rozet-kaynak" }, metin(kaynak)));
  if (egilim) ek.push(oge("span", { class: "rozet-egilim" }, metin(egilim)));
  if (yenileDugmesi) ek.push(oge("button", { type: "button" }, metin("yenile")));
  if (notMetni) ek.push(oge("p", { class: "rozet-not" }, metin(notMetni)));
  return cekirdek({ baslik, deger, birim }, ...ek);
}

// (b) Yuva tabanli bilesim: bilesen deligi birakir, doldurmayi cagirana birakir.
const rozetYuvali = ({ baslik, deger, birim, ek = [] }) => cekirdek({ baslik, deger, birim }, ...ek);

// Iki yaklasimin ayni girdi icin urettigi agaclari karsilastir.
const yaz = (d, girinti = 0) => {
  const satir = d.metin !== undefined
    ? `${"  ".repeat(girinti)}"${d.metin}"`
    : `${"  ".repeat(girinti)}${d.ad}${d.ozn.class ? "." + d.ozn.class : ""}`;
  return [satir, ...d.cocuklar.flatMap((c) => yaz(c, girinti + 1))];
};

const sarmalanan = rozetSarmalayanV2({
  baslik: "Sıcaklık", deger: -4.2, birim: "°C",
  esikAsildi: true, kaynakGoster: true, kaynak: "istasyon-3",
  egilim: null, yenileDugmesi: false, notMetni: null,
});

const yuvali = rozetYuvali({
  baslik: "Sıcaklık", deger: -4.2, birim: "°C",
  ek: [
    oge("span", { class: "rozet-uyari" }, metin("esik asildi")),
    oge("small", { class: "rozet-kaynak" }, metin("istasyon-3")),
  ],
});

console.log("sarmalanan agac:");
console.log(yaz(sarmalanan).join("\n"));
console.log("\nyuvali agac:");
console.log(yaz(yuvali).join("\n"));
console.log("\nagaclar ayni mi:", JSON.stringify(sarmalanan) === JSON.stringify(yuvali));

// Olcum: ihtiyac sayisi arttikca iki yaklasimin yuzeyi nasil buyuyor?
const govde = (f) => f.toString().split("\n").length;
const mantiksal = (f) => (f.toString().match(/\bif \(/g) ?? []).length;
console.log("\nyaklasim                 gövde satırı  dallanma  çağıran tarafın seçenekleri");
for (const [ad, f, ihtiyac] of [
  ["sarmalayan (2 ihtiyaç)", rozetSarmalayanV1, 2],
  ["sarmalayan (5 ihtiyaç)", rozetSarmalayanV2, 5],
  ["yuva tabanlı", rozetYuvali, 0],
]) {
  console.log(
    `${ad.padEnd(24)} ${String(govde(f)).padStart(12)} ${String(mantiksal(f)).padStart(9)} ` +
      `${(ihtiyac ? 2 ** ihtiyac : "sınırsız").toString().padStart(28)}`
  );
}
```

```
sarmalanan agac:
div.rozet
  span.rozet-baslik
    "Sıcaklık"
  output.rozet-deger
    "-4.2 °C"
  span.rozet-uyari
    "esik asildi"
  small.rozet-kaynak
    "istasyon-3"

yuvali agac:
div.rozet
  span.rozet-baslik
    "Sıcaklık"
  output.rozet-deger
    "-4.2 °C"
  span.rozet-uyari
    "esik asildi"
  small.rozet-kaynak
    "istasyon-3"

agaclar ayni mi: true

yaklasim                 gövde satırı  dallanma  çağıran tarafın seçenekleri
sarmalayan (2 ihtiyaç)              6         2                            4
sarmalayan (5 ihtiyaç)             10         5                           32
yuva tabanlı                        1         0                     sınırsız
```

İki ağaç birebir aynı. Verilen bir durumda hangi kalıbın kullanıldığı çıktıdan anlaşılmaz;
fark yalnızca kaynak metinde ve değişim maliyetindedir.

Tablo bu maliyeti veriyor. Beş mantıksal özellikli sarmalayan bileşen 32 ayrı özellik
birleşimi tanımlar. Bu birleşimlerin tümü anlamlı değildir — yenileme düğmesi ile not
satırı bir arada belki hiç istenmez — ama bileşen hepsini kabul eder ve hiçbirini
sınamamıştır. Beş dallanma, beş özelliğin birbirini nasıl etkilediğine dair beş sessiz
karardır: eklenme sırası sabittir ve çağıran değiştiremez.

Yuva tabanlı sürümde gövde tek satırdır ve dallanma yoktur. Çağıranın seçenekleri
sayılamaz, çünkü çağıran istediği ağacı verebilir. Bileşen bu ağaç hakkında hiçbir varsayım
yapmaz; yalnızca çekirdeğin sonuna ekler.

Asıl ayrım altıncı istekte görünür. Sarmalayan kalıpta altıncı istek bileşenin gövdesini
yeniden açar: yeni bir özellik, yeni bir dallanma, yeniden gözden geçirilecek bir dosya.
Yuva tabanlı kalıpta bileşene dokunulmaz; yeni düğüm çağıran tarafta kurulur.

#### Özellik Zinciri

Seçenek patlaması bileşenin kendi gövdesinde birikir. İkinci maliyet ise ağaç boyunca
yayılır: bir değeri kullanan bileşen, o değeri üreten bileşenin uzağındaysa aradaki her
katman değeri taşımak zorundadır. Buna **özellik zinciri (prop drilling)** denir.

Ölçüm tablosunda birim sistemi tercihini sayfa düzeyi biliyor, biçimlendirmeyi ise hücre
yapıyor. Arada tablo, gövde ve satır var.

```js
// ozellik-zinciri.mjs — ara katmanlarin yalnizca tasidigi ozelliklerin sayilmasi
const oge = (ad, ozn = {}, ...cocuklar) => ({ ad, ozn, cocuklar: cocuklar.flat().filter(Boolean) });
const metin = (deger) => ({ ad: "#metin", ozn: {}, cocuklar: [], metin: deger });
const bicim = (c, sistem) => (sistem === "si" ? `${c.toFixed(1)} °C` : `${(c * 1.8 + 32).toFixed(1)} °F`);

// Her bilesen aldigi ozellikleri ve bunlarin kacini kendisi kullandigini bildirir.
const defter = [];
const kaydet = (ad, alinan, kullanilan) =>
  defter.push({ ad, alinan, kullanilan, tasinan: alinan.filter((a) => !kullanilan.includes(a)) });

// (a) Zincirli akis: birim sistemi ust katmandan en alta kadar her imzada yeniden yazilir.
function zDeger({ santigrat, birimSistemi }) {
  kaydet("Değer", ["santigrat", "birimSistemi"], ["santigrat", "birimSistemi"]);
  return oge("output", {}, metin(bicim(santigrat, birimSistemi)));
}
function zSatir({ baslik, santigrat, birimSistemi }) {
  kaydet("Satır", ["baslik", "santigrat", "birimSistemi"], ["baslik"]);
  return oge("tr", {}, oge("th", {}, metin(baslik)), oge("td", {}, zDeger({ santigrat, birimSistemi })));
}
function zGovde({ olcumler, birimSistemi }) {
  kaydet("Gövde", ["olcumler", "birimSistemi"], ["olcumler"]);
  return oge("tbody", {}, ...olcumler.map((o) => zSatir({ ...o, birimSistemi })));
}
function zTablo({ baslik, olcumler, birimSistemi }) {
  kaydet("Tablo", ["baslik", "olcumler", "birimSistemi"], ["baslik"]);
  return oge("table", {}, oge("caption", {}, metin(baslik)), zGovde({ olcumler, birimSistemi }));
}

// (b) Yuva tabanli akis: deger dugumu, birim sisteminin bilindigi yerde kurulur;
// ara katmanlar hazir dugumu tasir, veriyi degil.
function yDeger({ santigrat, birimSistemi }) {
  kaydet("Değer/yuva", ["santigrat", "birimSistemi"], ["santigrat", "birimSistemi"]);
  return oge("output", {}, metin(bicim(santigrat, birimSistemi)));
}
function ySatir({ baslik, hucre }) {
  kaydet("Satır/yuva", ["baslik", "hucre"], ["baslik", "hucre"]);
  return oge("tr", {}, oge("th", {}, metin(baslik)), oge("td", {}, hucre));
}
function yGovde({ satirlar }) {
  kaydet("Gövde/yuva", ["satirlar"], ["satirlar"]);
  return oge("tbody", {}, ...satirlar);
}
function yTablo({ baslik, govde }) {
  kaydet("Tablo/yuva", ["baslik", "govde"], ["baslik", "govde"]);
  return oge("table", {}, oge("caption", {}, metin(baslik)), govde);
}

const olcumler = [{ baslik: "Üst yamaç", santigrat: -4.2 }, { baslik: "Alt yamaç", santigrat: 1.6 }];
const sistem = "imperyal";

const zincirli = zTablo({ baslik: "Kuzey Yamaç", olcumler, birimSistemi: sistem });
const yuvali = yTablo({
  baslik: "Kuzey Yamaç",
  govde: yGovde({
    satirlar: olcumler.map((o) =>
      ySatir({ baslik: o.baslik, hucre: yDeger({ santigrat: o.santigrat, birimSistemi: sistem }) })),
  }),
});

const yaz = (d, g = 0) => [
  d.metin !== undefined ? `${"  ".repeat(g)}"${d.metin}"` : `${"  ".repeat(g)}${d.ad}`,
  ...d.cocuklar.flatMap((c) => yaz(c, g + 1)),
];
console.log("zincirli akışın ürettiği ağaç:");
console.log(yaz(zincirli).join("\n"));
console.log("iki ağaç aynı mı:", JSON.stringify(zincirli) === JSON.stringify(yuvali));

const ozet = new Map();
for (const k of defter) ozet.set(k.ad, k);
console.log("\nbileşen       aldığı  kullandığı  yalnızca taşıdığı");
for (const [ad, k] of ozet)
  console.log(`${ad.padEnd(13)} ${String(k.alinan.length).padStart(6)} ${String(k.kullanilan.length).padStart(11)}  ${k.tasinan.join(", ") || "-"}`);

const topla = (ek) => [...ozet.values()].filter((k) => k.ad.includes("/yuva") === ek).reduce((t, k) => t + k.tasinan.length, 0);
console.log(`\nzincirli akışta yalnızca taşınan özellik sayısı: ${topla(false)}`);
console.log(`yuva tabanlı akışta yalnızca taşınan özellik sayısı: ${topla(true)}`);
```

```
zincirli akışın ürettiği ağaç:
table
  caption
    "Kuzey Yamaç"
  tbody
    tr
      th
        "Üst yamaç"
      td
        output
          "24.4 °F"
    tr
      th
        "Alt yamaç"
      td
        output
          "34.9 °F"
iki ağaç aynı mı: true

bileşen       aldığı  kullandığı  yalnızca taşıdığı
Tablo              3           1  olcumler, birimSistemi
Gövde              2           1  birimSistemi
Satır              3           1  santigrat, birimSistemi
Değer              2           2  -
Değer/yuva         2           2  -
Satır/yuva         2           2  -
Gövde/yuva         1           1  -
Tablo/yuva         2           2  -

zincirli akışta yalnızca taşınan özellik sayısı: 5
yuva tabanlı akışta yalnızca taşınan özellik sayısı: 0
```

Zincirli akışta üç ara katmanın hiçbiri birim sistemini kullanmıyor; üçü de yalnızca
aşağıya geçiriyor. Sayı beş, çünkü ölçüm verisi de aynı yolu izliyor. Yuva tabanlı akışta
bu sayı sıfır: değer düğümü, birim sisteminin zaten bilindiği yerde kuruluyor ve ara
katmanlar hazır düğümü taşıyor.

Zincirin gerçek bedeli imzalarda değil, bağımlılıktadır. Satır bileşeni birim sistemini
imzasında taşıdığı için, birim sisteminin varlığından haberdar olmuş olur. Bu özellik
kaldırıldığında ya da adı değiştiğinde, onu hiç kullanmayan üç bileşen de değişmek
zorundadır. Yuva tabanlı akışta satır bileşeni bir hücre alır; hücrenin neye göre
biçimlendirildiğini bilmez ve bilmediği şey değiştiğinde etkilenmez.

Bu, Bağlam Aktarımı dersindeki mekanizmanın neden var olduğunu da açıklar. Bağlam, zinciri
kesmenin ikinci yoludur: değer ağacın bir noktasında sağlanır ve isteyen düğüm doğrudan okur.
Yuva tabanlı bileşim ile bağlam birbirinin yerine geçmez — biri **yapıyı**, diğeri
**veriyi** aşağı taşır. Ölçüm tablosunda hücrenin nasıl görüneceği bir yapı kararıdır ve
yuvaya aittir; birim sistemi tercihi ise sayfanın her yerinde okunan bir veridir ve bağlama
aittir.

#### Kalıtım Yerine Bileşim

Sarmalama kalıbının ucundaki bir uygulama, bileşenleri kalıtımla türetmektir: temel rozet
sınıfı, ondan türeyen uyarı rozeti, ondan türeyen eğilimli uyarı rozeti. Bu yol arayüz
ağaçlarında iki nedenle tıkanır.

Birincisi, kalıtım tek eksenlidir. Uyarı gösteren rozetle kaynağı gösteren rozet ayrı
dallardaysa, ikisini birden isteyen üçüncü bir rozet için ortak bir ata yoktur; her yeni
birleşim yeni bir sınıf ister. Beş bağımsız istek 32 sınıfa çıkar. Yuva tabanlı bileşimde
aynı 32 birleşim tek bileşenin 32 farklı çağrısıdır.

İkincisi, kalıtım gövdenin içine erişim verir; bileşim vermez. Türeyen sınıf atasının iç
yapısına yaslanır, ata değişince türeyen kırılır. Bileşimde bileşenin dışarıya söz verdiği
tek şey özellik yüzeyidir; iç yapısı sözleşmenin parçası değildir.

#### Sarmalama Ne Zaman Doğrudur

Yuva tabanlı bileşimin üstünlüğü koşulsuz değildir. Sarmalama, bileşenin **kararı sahiplenmesi**
gerektiği yerde doğrudur.

Süzgeç panelindeki eşik alanı buna örnektir. Alanın etiketi, hata iletisi ve geçersizlik
bildirimi arasındaki ilişki sabit olmalıdır: etiket alana bağlanmalı, hata iletisi alana
ek açıklama olarak iliştirilmeli, geçersizlik durumu her ikisine de yansımalıdır. Bu ilişki
çağırana bırakılırsa her çağrıda yeniden kurulur ve bir yerde unutulur. Bileşen burada
deliği bırakmaz; yapıyı kendi içinde kilitler ve dışarıya yalnızca etiket metniyle hata
iletisini alan bir yüzey sunar.

Ölçüt şudur: yapı **doğruluk** taşıyorsa sarmalanır, yalnızca **görünüm** taşıyorsa yuvaya
bırakılır. Rozetin sonuna ne eklendiği bir görünüm kararıdır; etiketin alana nasıl
bağlandığı bir doğruluk kararıdır. Bir bileşende ikisi bir arada bulunabilir: rozet
çekirdeği kilitli, sonu açıktır.

#### Özet

- Bileşimin iki kalıbı vardır: sarmalayan bileşen yapıyı içeride sabitler, yuva tabanlı
  bileşim yapının bir bölümünü çağırandan alır.
- İki kalıp aynı ağacı üretebilir; ayrım çıktıda değil, yeni bir istek geldiğinde hangi
  dosyanın açıldığındadır.
- Mantıksal özelliklerle kurulan sarmalayan bileşende seçenek sayısı üstel büyür; beş
  bağımsız istek 32 birleşim ve beş dallanma demektir.
- Özellik zinciri, ara katmanların kullanmadığı özellikleri taşımasıdır; ölçüsü "yalnızca
  taşınan özellik" sayısıdır ve yuva tabanlı bileşimde sıfıra iner.
- Yuva yapıyı, bağlam veriyi aşağı taşır; ikisi birbirinin yerine geçmez.
- Doğruluk taşıyan yapı sarmalanır, yalnızca görünüm taşıyan yapı yuvaya bırakılır.

#### Sonraki Adım

Bu ders yapının yeniden kullanımını çözdü: aynı ağacı farklı içeriklerle kurmanın yolu
belli. Ancak bileşenlerde yeniden kullanılan tek şey yapı değildir. Süzgeç panelindeki
arama alanı girdiyi geciktirerek tetikliyor, ölçüm tablosu sıralama tercihini adres
çubuğuyla eşitliyor, rozet aralıklı olarak yenileniyor. Üçünün de içinde durum, yan etki ve
temizlik var; üçü de birbirinden bağımsız yazılmış ve üçü de aynı hataları tekrar ediyor.
Bu mantık bir bileşene bağlı olduğu sürece taşınamaz. Sonraki ders, durumu ve yan etkiyi
işaretleme üretmeden paketleyen birimi kurar ve bu birimlerin birbirine eklenmesinin hangi
kurala bağlı olduğunu gösterir.

### Özel Kancalar ve Birleştirilebilir İşlevler

Source: https://academia.sh/tr/kurslar/bilesen-tabanli-gelistirme/ozel-kancalar-ve-birlestirilebilir-islevler

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ı.

Ö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 ve 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**.

```js
// 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 ve 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.

```js
// 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 ve 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.

### Bileşik Bileşenler

Source: https://academia.sh/tr/kurslar/bilesen-tabanli-gelistirme/bilesik-bilesenler

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.

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 ve 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.

```js
// 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 beşe çıkar.

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.

### Sunum ve Konteyner Ayrımı

Source: https://academia.sh/tr/kurslar/bilesen-tabanli-gelistirme/sunum-ve-konteyner-ayrimi

Veriyi getiren sorumlulukla görünümü üreten sorumluluğun ayrılması; ayrımın sahte bağımlılık sayısıyla ölçülmesi, tek sunum bileşeninin iki konteynerle kullanılması ve ayrımın aşırıya kaçtığı yer.

Ölçüm tablosu şu anda dört iş yapıyor: ölçümleri ağdan getiriyor, kullanıcının sıralama
tercihini depolamadan okuyuyor, satırları sıralıyor ve bir tablo ağacı üretiyor. Dördü de
aynı işlevin gövdesinde.

Bu bileşenin boş tabloyla nasıl göründüğünü sınamak isteyen biri, önce sahte bir ağ
kaynağı kurmak zorundadır. Aynı tabloyu bir arşiv dosyasındaki ölçümlerle çizmek isteyen
biri, bileşenin içindeki ağ çağrısını değiştirmek zorundadır. İki istek de görünümle ilgili
değil, ama ikisi de veri katmanına çarpıyor.

#### İki Sorumluluk

Ayrım şu iki rolü tanımlar.

**Sunum bileşeni (presentational component)** yalnızca aldığı özelliklerden bir ağaç üretir.
Ağa erişmez, saati okumaz, depolamaya bakmaz, üst kapsamdan değer almaz. Aynı özelliklerle
her zaman aynı ağacı verir; Programlama Temelleri kursunda tanımlanan saf fonksiyon tanımına
uyar.

**Konteyner bileşeni (container component)** veriyi elde eder, biçime sokar ve sunum
bileşenine özellik olarak verir. Kendisi işaretleme üretmez; ürettiği tek şey bir özellik
kümesidir. (Terim, düzen bağlamındaki esnek kapsayıcı ve ızgara kapsayıcısıyla karışmasın
diye "kapsayıcı" değil **konteyner** yazılır.)

Ayrımın kuralı tek cümleye iner: sunum bileşeninin girdisi imzasında **eksiksiz** yazılıdır.
İmzasında yazmayan hiçbir şeye bağlı değilse ayrım yapılmıştır.

#### Ayrımın Ölçüsü

Kazanç, sınama için kurulması gereken sahte bağımlılık sayısıyla ölçülebilir.

```js
// ayrim.mjs — birlesik bilesen ile sunum/konteyner ayriminin sinanabilirlik farki
import { deepStrictEqual } from "node:assert";

const oge = (ad, ozn = {}, ...cocuklar) => ({ ad, ozn, cocuklar: cocuklar.flat().filter(Boolean) });
const metin = (deger) => ({ ad: "#metin", ozn: {}, cocuklar: [], metin: deger });

// --- Birlesik bilesen: veriyi de getirir, siralar, bicimler ve cizer ---
async function tabloBirlesik({ kaynak, saat, depo }) {
  const alan = depo.oku("siralama") ?? "ad";
  const ham = await kaynak.getir();                      // ag
  const satirlar = [...ham].sort((a, b) => (a[alan] > b[alan] ? 1 : -1));
  const damga = new Date(saat.simdi()).toISOString();    // zaman
  return oge("table", {},
    oge("caption", {}, metin(`Kuzey Yamaç — ${damga}`)),
    oge("tbody", {}, ...satirlar.map((s) =>
      oge("tr", { "data-ad": s.ad },
        oge("th", {}, metin(s.ad)),
        oge("td", {}, metin(`${s.deger.toFixed(1)} °C`))))));
}

// --- Ayrilmis surum ---
// Sunum bileseni: yalnizca ozelliklerinden agac uretir. Ag yok, zaman yok, depolama yok.
const tabloGorunumu = ({ baslik, damga, satirlar }) =>
  oge("table", {},
    oge("caption", {}, metin(`${baslik} — ${damga}`)),
    oge("tbody", {}, ...satirlar.map((s) =>
      oge("tr", { "data-ad": s.ad },
        oge("th", {}, metin(s.ad)),
        oge("td", {}, metin(`${s.deger.toFixed(1)} °C`))))));

// Konteyner 1: canli olcum kaynagi.
async function canliKonteyner({ kaynak, saat, depo }) {
  const alan = depo.oku("siralama") ?? "ad";
  const ham = await kaynak.getir();
  return tabloGorunumu({
    baslik: "Kuzey Yamaç",
    damga: new Date(saat.simdi()).toISOString(),
    satirlar: [...ham].sort((a, b) => (a[alan] > b[alan] ? 1 : -1)),
  });
}

// Konteyner 2: arsiv dosyasindan okunan ayni bicimdeki veri.
function arsivKonteyneri({ arsivSatirlari, damga }) {
  return tabloGorunumu({
    baslik: "Kuzey Yamaç",
    damga,
    satirlar: [...arsivSatirlari].sort((a, b) => (a.ad > b.ad ? 1 : -1)),
  });
}

// --- Sinama: kac sahte bagimlilik gerekiyor? ---
const OLCUMLER = [{ ad: "Üst yamaç", deger: -4.2 }, { ad: "Alt yamaç", deger: 1.63 }];
const DAMGA = "2026-02-11T06:00:00.000Z";

let sahte = 0;
const sahteKaynak = (v) => { sahte++; return { getir: async () => v }; };
const sahteSaat = (t) => { sahte++; return { simdi: () => t }; };
const sahteDepo = (d) => { sahte++; return { oku: (a) => d[a] }; };

sahte = 0;
const birlesikAgac = await tabloBirlesik({
  kaynak: sahteKaynak(OLCUMLER), saat: sahteSaat(Date.parse(DAMGA)), depo: sahteDepo({ siralama: "ad" }),
});
const birlesikSahte = sahte;

sahte = 0;
const gorunumAgaci = tabloGorunumu({
  baslik: "Kuzey Yamaç", damga: DAMGA,
  satirlar: [{ ad: "Alt yamaç", deger: 1.63 }, { ad: "Üst yamaç", deger: -4.2 }],
});
const gorunumSahte = sahte;

deepStrictEqual(birlesikAgac, gorunumAgaci);
console.log("birleşik bileşenin ağacı ile sunum bileşeninin ağacı aynı: evet");
console.log(`sınama için gereken sahte bağımlılık — birleşik: ${birlesikSahte}, sunum: ${gorunumSahte}`);
console.log(`sınamanın eşzamansız olması gerekiyor mu — birleşik: evet, sunum: hayır`);

// Saflik: ayni ozelliklerle iki cagri ayni agaci verir.
const a = tabloGorunumu({ baslik: "Kuzey Yamaç", damga: DAMGA, satirlar: OLCUMLER });
const b = tabloGorunumu({ baslik: "Kuzey Yamaç", damga: DAMGA, satirlar: OLCUMLER });
deepStrictEqual(a, b);
console.log("sunum bileşeni aynı özelliklerle aynı ağacı üretiyor: evet");

// Iki konteyner, tek sunum bileseni.
sahte = 0;
const canli = await canliKonteyner({
  kaynak: sahteKaynak(OLCUMLER), saat: sahteSaat(Date.parse(DAMGA)), depo: sahteDepo({}),
});
const canliSahte = sahte;
sahte = 0;
const arsiv = arsivKonteyneri({ arsivSatirlari: OLCUMLER, damga: DAMGA });
const arsivSahte = sahte;
deepStrictEqual(canli, arsiv);
console.log(`\niki konteynerin ürettiği ağaç aynı: evet (canlı sahte=${canliSahte}, arşiv sahte=${arsivSahte})`);

// Gorunum durumlarinin es zamanli sayilabilirligi
const durumlar = [
  ["dolu tablo", { baslik: "Kuzey Yamaç", damga: DAMGA, satirlar: OLCUMLER }],
  ["tek satır", { baslik: "Kuzey Yamaç", damga: DAMGA, satirlar: [OLCUMLER[0]] }],
  ["boş tablo", { baslik: "Kuzey Yamaç", damga: DAMGA, satirlar: [] }],
  ["negatif ve sıfır", { baslik: "Kuzey Yamaç", damga: DAMGA, satirlar: [{ ad: "Zirve", deger: -0.04 }] }],
];
console.log("\ndurum              satır  ilk hücre");
for (const [ad, ozellikler] of durumlar) {
  const agac = tabloGorunumu(ozellikler);
  const govde = agac.cocuklar[1];
  const ilk = govde.cocuklar[0]?.cocuklar[1].cocuklar[0].metin ?? "-";
  console.log(`${ad.padEnd(18)} ${String(govde.cocuklar.length).padStart(5)}  ${ilk}`);
}
```

```
birleşik bileşenin ağacı ile sunum bileşeninin ağacı aynı: evet
sınama için gereken sahte bağımlılık — birleşik: 3, sunum: 0
sınamanın eşzamansız olması gerekiyor mu — birleşik: evet, sunum: hayır
sunum bileşeni aynı özelliklerle aynı ağacı üretiyor: evet

iki konteynerin ürettiği ağaç aynı: evet (canlı sahte=3, arşiv sahte=0)

durum              satır  ilk hücre
dolu tablo             2  -4.2 °C
tek satır              1  -4.2 °C
boş tablo              0  -
negatif ve sıfır       1  -0.0 °C
```

Üç sahteye karşı sıfır. Birleşik bileşeni sınamak için ağ kaynağı, saat ve depolama taklit
edilmek zorunda; üçü de görünümle ilgisiz. Sunum bileşeni için hiçbiri gerekmiyor, çünkü
üçü de imzada bir özellik olarak duruyor.

Sahte sayısı tek kazanç değil. Birleşik bileşen eşzamansızdır; sınaması bir sözü beklemek
zorundadır ve zamanlamaya bağlı kırılganlık taşır. Sunum bileşeni eşzamanlıdır: çağrılır ve
sonucu incelenir.

#### Konteyner Değişir, Görünüm Değişmez

İkinci bölüm ayrımın asıl kazancını gösteriyor. Canlı konteyner ağdan okuyor ve üç sahte
istiyor; arşiv konteyneri elindeki diziyi kullanıyor ve hiç sahte istemiyor. İkisinin
ürettiği ağaç birebir aynı.

Bu, aynı görünümün istasyon sayfasında canlı ölçümlerle, arşiv sayfasında geçmiş
ölçümlerle ve sunucuda önceden üretilen bir sayfada dosyadan okunan ölçümlerle
kullanılabilmesi demektir. Görünüm bileşeni bu üç bağlamın hiçbirini bilmez.

Kural şudur: konteyner **nereden** sorusunu, sunum bileşeni **nasıl görünür** sorusunu
yanıtlar. İki soru bağımsız değiştiği için iki bileşen de bağımsız değişir.

#### Ayrımın Görünür Kıldığı

Son tablo, ayrımın sınama dışında da bir şey kazandırdığını gösteriyor. Dört görünüm
durumu ağ olmadan, saniyeler içinde ve yan yana üretildi. Dördüncüsünde bir kusur ortaya
çıktı: -0.04 santigrat derece, tek ondalığa yuvarlanınca `-0.0 °C` oluyor.

Bu, Arayüz Tasarımının Temelleri kursundaki İşlevsel Renkler ve mikro metin tartışmalarının
somut bir örneğidir: negatif işareti taşıyan bir sıfır, okuyucuya sıcaklığın donma
noktasının altında olduğunu söyler; oysa değer neredeyse tam sıfırdır. Kusur bir
biçimlendirme kararıdır ve ağ katmanıyla hiç ilgisi yoktur — ama birleşik bileşende ancak
sahte bir kaynak kurup uygun veriyi ona yerleştirerek görülebilirdi.

Görünüm durumlarının ucuz üretilebilmesi, tasarım tarafındaki durum tablosunun gerçekten
denetlenebilmesi anlamına gelir. Arayüz Tasarımının Temelleri kursundaki Bileşen Durumları
konusunda tanımlanan beş etkileşim durumu, yükleme
ve boş durumlar, hata durumları — hepsi birer özellik kümesidir ve hepsi sıfır sahteyle
üretilebilir.

#### Ayrım Nerede Karşılığını Vermez

Her bileşeni ikiye bölmek bir kazanç değildir.

Bir bileşenin hiç dış bağımlılığı yoksa zaten sunum bileşenidir; ona bir konteyner eklemek
yalnızca bir dosya ve bir dolaylılık katmanı ekler. Ölçüm rozeti buna örnektir: girdisi
zaten ölçümün adı, değeri ve eşiğidir.

Tersi de geçerlidir. Yalnızca tek bir yerde kullanılan, hiç yeniden kullanılmayacak bir
görünümü ayırmak, iki dosya arasında gidip gelmeyi zorunlu kılar ve okuma maliyetini
artırır. Ayrım, ikinci bir kullanım ya da sınanması gereken bir görünüm durumu ortaya
çıktığında karşılığını verir.

Üçüncü bir yanlış uygulama, ayrımın **dosya düzeyinde** yapılıp bağımlılığın kaçırılmasıdır.
Sunum bileşeni ayrı bir dosyada durup içinde saati okuyorsa ya da bir küresel değerden
biçim ayarını alıyorsa ayrım yapılmamıştır; sahte sayısı düşmez. Ölçü, dosya sayısı değil
imzanın eksiksizliğidir.

Ayrımın bir başka biçimi, konteynerin kendisinin bir bileşen bile olmamasıdır: önceki
derste kurulan özel kanca ya da birleştirilebilir işlev, veriyi elde etmeyi üstlenir
ve bileşen yalnızca onu çağırıp sonucu sunum bileşenine verir. Sorumluluk ayrımı aynıdır;
değişen, ayrımın hangi birimde yapıldığıdır.

#### Özet

- Sunum bileşeni yalnızca özelliklerinden ağaç üretir; ağ, saat, depolama ve üst kapsam
  okumaz. Konteyner veriyi elde eder ve özellik kümesine çevirir.
- Ayrımın ölçüsü, sınama için kurulması gereken sahte bağımlılık sayısıdır; örnekte üçe
  karşı sıfır çıktı ve sunum bileşeninin sınaması eşzamanlı kaldı.
- Tek sunum bileşeni farklı konteynerlerle aynı ağacı üretir; canlı veri, arşiv verisi ve
  önceden üretilmiş sayfa aynı görünümü paylaşır.
- Görünüm durumlarının ucuz üretilebilmesi biçimlendirme kusurlarını ağ katmanına hiç
  dokunmadan görünür kılar.
- Dış bağımlılığı olmayan bileşene konteyner eklemek yalnızca dolaylılık ekler; ayrım
  ikinci kullanım ya da sınanacak durum ortaya çıktığında karşılığını verir.
- Ölçü dosya sayısı değil imzanın eksiksizliğidir; ayrı dosyadaki bir bileşen küresel değer
  okuyorsa ayrım yapılmamıştır.

#### Sonraki Adım

Bu ders veriyi görünümden ayırdı, ama davranışı ayırmadı. Süzgeç panelindeki seçenek listesi
düşünüldüğünde geriye şu kalır: ok tuşlarıyla gezinme, ilk ve son ögeye atlama, harfe
basınca eşleşen ögeye gitme, seçim ve kapanma. Bu davranış her yerde aynıdır, ama listenin
görünümü her yerde farklıdır — bir yerde açılır liste, bir yerde yan panel, bir yerde etiket
kümesi. Davranışı bir bileşene gömmek, görünümü de onunla birlikte sabitlemek demektir.
Sonraki ders davranışı görünümden koparır ve bunun için gereken şeyin bir bileşen değil, saf
bir durum makinesi olduğunu gösterir.

### Başsız Bileşenler

Source: https://academia.sh/tr/kurslar/bilesen-tabanli-gelistirme/bassiz-bilesenler

Davranışın görünümden koparılması; seçenek listesinin saf durum makinesi olarak yazılması, erişilebilirlik özniteliklerinin durumdan türetilmesi, olay dizisiyle sınanması ve aynı davranışın iki görünümle kullanılması.

Süzgeç panelinde istasyon seçmek için bir liste var. Listenin davranışı ayrıntılıdır: ok
tuşlarıyla gezinme, kapalı öğeyi atlama, listenin ucunda başa dönme, ilk ve son öğeye
atlama, harfe basınca eşleşen öğeye gitme, seçim, iptal. Bu davranış aynı zamanda bir
erişilebilirlik sözleşmesi taşır — hangi öğenin etkin olduğu, listenin açık olup olmadığı ve
hangi öğenin seçili olduğu yardımcı teknolojiye bildirilmek zorundadır.

Görünüm ise her yerde farklı. Süzgeç panelinde düğmeye bağlı açılır bir liste, geniş
ekranda yan panelde her zaman açık duran bir liste, ölçüm tablosunun başlığında satır içi
bir seçici. Davranışı bir bileşene gömmek, bu üç görünümü tek bir görünüme indirmek demektir.

#### Davranış Bir Bileşen Değildir

**Başsız bileşen (headless component)**, davranışı ve erişilebilirlik sözleşmesini taşıyan
ama hiçbir görünüm üretmeyen birimdir. Dışarıya iki şey verir: okunacak bir durum ve
işaretlemeye takılacak öznitelikler. Ne öge seçer, ne sınıf yazar, ne biçem tanımlar.

Önceki dersteki ayrımın bir adım ilerisidir. Sunum ve konteyner ayrımı veriyi görünümden
ayırmıştı; burada ayrılan şey davranıştır. Sonuçta üç bağımsız katman kalır: veriyi elde
eden, davranışı yürüten ve görünümü üreten.

Davranışın taşıyıcısı bir durum makinesidir. Programlama Temelleri kursundaki saf fonksiyon
tanımına uyan bir indirgeyici — bir durum ve bir olay alır, yeni durum döndürür — davranışın
tamamını taşıyabilir. Zamanlayıcı, DOM erişimi ve ağ çağrısı bu işlevin dışında kalır.

```js
// bassiz.mjs — secenek listesinin davranisi: saf durum makinesi, gorunum yok
export const SECENEKLER = [
  { deger: "ust", metin: "Üst yamaç" },
  { deger: "orta", metin: "Orta teras" },
  { deger: "alt", metin: "Alt yamaç", pasif: true },
  { deger: "zirve", metin: "Zirve" },
];

export const baslangic = (secili = null) =>
  ({ acik: false, etkin: -1, secili, arabellek: "", sonHarfZamani: -Infinity });

const secilebilir = (s, i) => i >= 0 && i < s.length && !s[i].pasif;
const uc = (s, bastan) =>
  (bastan ? s.map((_, i) => i) : s.map((_, i) => s.length - 1 - i)).find((i) => secilebilir(s, i)) ?? -1;
const ilerle = (s, baslangicDizini, yon) => {
  if (baslangicDizini < 0) return uc(s, yon > 0);
  for (let adim = 1; adim <= s.length; adim++) {
    const i = (baslangicDizini + yon * adim + s.length * adim) % s.length;
    if (secilebilir(s, i)) return i;
  }
  return -1;
};

// Saf indirgeyici: (durum, olay) -> yeni durum. Yan etki, zamanlayici ve DOM yok.
export function indirge(durum, olay, secenekler = SECENEKLER) {
  const ac = () => {
    const seciliDizin = secenekler.findIndex((o) => o.deger === durum.secili);
    return {
      ...durum, acik: true, arabellek: "",
      etkin: secilebilir(secenekler, seciliDizin) ? seciliDizin : uc(secenekler, true),
    };
  };
  const kapat = () => ({ ...durum, acik: false, etkin: -1, arabellek: "" });

  switch (olay.t) {
    case "ac": return durum.acik ? durum : ac();
    case "kapat": return kapat();
    case "isaretle":
      return durum.acik && secilebilir(secenekler, olay.dizin) ? { ...durum, etkin: olay.dizin } : durum;
    case "tus":
      switch (olay.tus) {
        case "ArrowDown": return durum.acik ? { ...durum, etkin: ilerle(secenekler, durum.etkin, +1) } : ac();
        case "ArrowUp": return durum.acik ? { ...durum, etkin: ilerle(secenekler, durum.etkin, -1) } : ac();
        case "Home": return durum.acik ? { ...durum, etkin: uc(secenekler, true) } : durum;
        case "End": return durum.acik ? { ...durum, etkin: uc(secenekler, false) } : durum;
        case "Escape": return kapat();
        case "Enter": case " ":
          if (!durum.acik) return ac();
          if (!secilebilir(secenekler, durum.etkin)) return durum;
          return { ...kapat(), secili: secenekler[durum.etkin].deger };
        default: return durum;
      }
    case "harf": {
      const yeni = olay.zaman - durum.sonHarfZamani > 500 ? olay.harf : durum.arabellek + olay.harf;
      const kucuk = (m) => m.toLocaleLowerCase("tr");
      const bulunan = secenekler.findIndex((o, i) => secilebilir(secenekler, i) && kucuk(o.metin).startsWith(kucuk(yeni)));
      return {
        ...durum, acik: true, arabellek: yeni, sonHarfZamani: olay.zaman,
        etkin: bulunan === -1 ? durum.etkin : bulunan,
      };
    }
    default: return durum;
  }
}

// Durumdan turetilen erisilebilirlik oznitelikleri: yine gorunum degil, sozlesme.
export const aria = (durum, kok, secenekler = SECENEKLER) => ({
  tetikleyici: {
    "aria-haspopup": "listbox",
    "aria-expanded": String(durum.acik),
    "aria-controls": `${kok}-liste`,
  },
  liste: {
    id: `${kok}-liste`,
    role: "listbox",
    tabindex: -1,
    "aria-activedescendant": durum.acik && durum.etkin >= 0 ? `${kok}-secenek-${durum.etkin}` : "",
  },
  secenek: (i) => ({
    id: `${kok}-secenek-${i}`,
    role: "option",
    "aria-selected": String(secenekler[i].deger === durum.secili),
    ...(secenekler[i].pasif ? { "aria-disabled": "true" } : {}),
  }),
});
```

Durumda beş alan var ve hiçbiri görünüme ait değil: liste açık mı, hangi öğe etkin, hangi
değer seçili, harfle arama arabelleği ne ve son harf ne zaman girildi. Zaman bile bir
alandır — indirgeyici saati okumaz, olayın içinde alır. Bu, sınamayı zamandan bağımsız kılar.

Erişilebilirlik öznitelikleri durumun bir türevi olarak yazılır. Etkin öğe odağı almaz; liste
odaklı kalır ve etkin öğenin kimliği `aria-activedescendant` ile bildirilir. Tarayıcı ve Web
Platformu kursundaki dönen sekme durağı düzenine bir alternatiftir: klavye sırasında tek durak ve
listenin içinde ok tuşlarıyla gezinme ikisinde de ortaktır, ama odak öğeler arasında taşınmaz.

#### Aynı Davranış, İki Görünüm

Aşağıdaki dosya, önceki bloktaki `bassiz.mjs` ile aynı dizine konur.

```js
// bassiz-izle.mjs — ayni durum makinesi, iki ayri gorunum
import { SECENEKLER, baslangic, indirge, aria } from "./bassiz.mjs";

const dizi = [
  { t: "tus", tus: "ArrowDown" },
  { t: "tus", tus: "ArrowDown" },
  { t: "tus", tus: "ArrowDown" },
  { t: "harf", harf: "z", zaman: 1000 },
  { t: "tus", tus: "Home" },
  { t: "tus", tus: "ArrowUp" },
  { t: "tus", tus: "Enter" },
  { t: "tus", tus: "ArrowDown" },
  { t: "tus", tus: "Escape" },
];

let durum = baslangic();
console.log("olay                 açık  etkin  etkin metin   seçili  arabellek");
console.log(`${"(başlangıç)".padEnd(20)} ${String(durum.acik).padEnd(5)} ${String(durum.etkin).padStart(5)}  ${"-".padEnd(12)} ${String(durum.secili).padEnd(7)} "${durum.arabellek}"`);
for (const olay of dizi) {
  durum = indirge(durum, olay);
  const ad = olay.t === "tus" ? `tuş ${olay.tus}` : olay.t === "harf" ? `harf "${olay.harf}"` : olay.t;
  const etkinMetin = durum.etkin >= 0 ? SECENEKLER[durum.etkin].metin : "-";
  console.log(`${ad.padEnd(20)} ${String(durum.acik).padEnd(5)} ${String(durum.etkin).padStart(5)}  ${etkinMetin.padEnd(12)} ${String(durum.secili).padEnd(7)} "${durum.arabellek}"`);
}

// --- Ayni durum, iki gorunum ---
const oge = (ad, ozn = {}, ...cocuklar) => ({ ad, ozn, cocuklar: cocuklar.flat().filter(Boolean) });
const metin = (d) => ({ ad: "#metin", ozn: {}, cocuklar: [], metin: d });
const yaz = (d, g = 0) => {
  if (d.metin !== undefined) return [`${"  ".repeat(g)}"${d.metin}"`];
  const o = Object.entries(d.ozn).filter(([, v]) => v !== "").map(([a, v]) => ` ${a}="${v}"`).join("");
  return [`${"  ".repeat(g)}<${d.ad}${o}>`, ...d.cocuklar.flatMap((c) => yaz(c, g + 1))];
};

// Gorunum A: dugmeye bagli acilir liste.
const acilirListe = (durum, kok) => {
  const a = aria(durum, kok);
  const secili = SECENEKLER.find((o) => o.deger === durum.secili);
  return oge("div", { class: "acilir" },
    oge("button", { type: "button", ...a.tetikleyici }, metin(secili?.metin ?? "İstasyon seçin")),
    durum.acik && oge("ul", { ...a.liste, class: "acilir-liste" },
      ...SECENEKLER.map((s, i) => oge("li", { ...a.secenek(i) }, metin(s.metin)))));
};

// Gorunum B: yan panelde her zaman acik liste.
const yanPanel = (durum, kok) => {
  const a = aria({ ...durum, acik: true }, kok);
  return oge("nav", { class: "yan-panel" },
    oge("h2", { id: `${kok}-baslik` }, metin("İstasyon")),
    oge("ul", { ...a.liste, "aria-labelledby": `${kok}-baslik`, class: "panel-liste" },
      ...SECENEKLER.map((s, i) => oge("li", { ...a.secenek(i) }, metin(s.metin)))));
};

let d2 = baslangic();
for (const olay of [{ t: "ac" }, { t: "tus", tus: "ArrowDown" }]) d2 = indirge(d2, olay);

console.log("\ngörünüm A (açılır liste), durum: açık, etkin =", d2.etkin);
console.log(yaz(acilirListe(d2, "istasyon")).join("\n"));

// Iki gorunumu karsilastir: rol ve durum oznitelikleri ayni, oge adlari ve siniflar farkli.
const dolas = (d, c = []) => { if (d.metin === undefined) { c.push(d); d.cocuklar.forEach((x) => dolas(x, c)); } return c; };
const imza = (kok, on) => dolas(kok).map((d) => {
  const rol = d.ozn.role ?? d.ad;
  const durumlar = Object.entries(d.ozn)
    .filter(([a]) => a.startsWith("aria-"))
    .map(([a, v]) => `${a}=${String(v).replace(on, "*")}`).sort().join(",");
  return `${rol}${durumlar ? "[" + durumlar + "]" : ""}`;
});
const a = imza(acilirListe(d2, "istasyon"), /istasyon/g);
const b = imza(yanPanel(d2, "istasyon-yan"), /istasyon-yan/g);
console.log("\ngörünüm A rol/durum imzası:", JSON.stringify(a));
console.log("görünüm B rol/durum imzası:", JSON.stringify(b));
console.log("öge adları:", JSON.stringify(dolas(acilirListe(d2, "istasyon")).map((d) => d.ad)),
  "/", JSON.stringify(dolas(yanPanel(d2, "istasyon-yan")).map((d) => d.ad)));
```

```
olay                 açık  etkin  etkin metin   seçili  arabellek
(başlangıç)          false    -1  -            null    ""
tuş ArrowDown        true      0  Üst yamaç    null    ""
tuş ArrowDown        true      1  Orta teras   null    ""
tuş ArrowDown        true      3  Zirve        null    ""
harf "z"             true      3  Zirve        null    "z"
tuş Home             true      0  Üst yamaç    null    "z"
tuş ArrowUp          true      3  Zirve        null    "z"
tuş Enter            false    -1  -            zirve   ""
tuş ArrowDown        true      3  Zirve        zirve   ""
tuş Escape           false    -1  -            zirve   ""

görünüm A (açılır liste), durum: açık, etkin = 1
<div class="acilir">
  <button type="button" aria-haspopup="listbox" aria-expanded="true" aria-controls="istasyon-liste">
    "İstasyon seçin"
  <ul id="istasyon-liste" role="listbox" tabindex="-1" aria-activedescendant="istasyon-secenek-1" class="acilir-liste">
    <li id="istasyon-secenek-0" role="option" aria-selected="false">
      "Üst yamaç"
    <li id="istasyon-secenek-1" role="option" aria-selected="false">
      "Orta teras"
    <li id="istasyon-secenek-2" role="option" aria-selected="false" aria-disabled="true">
      "Alt yamaç"
    <li id="istasyon-secenek-3" role="option" aria-selected="false">
      "Zirve"

görünüm A rol/durum imzası: ["div","button[aria-controls=*-liste,aria-expanded=true,aria-haspopup=listbox]","listbox[aria-activedescendant=*-secenek-1]","option[aria-selected=false]","option[aria-selected=false]","option[aria-disabled=true,aria-selected=false]","option[aria-selected=false]"]
görünüm B rol/durum imzası: ["nav","h2","listbox[aria-activedescendant=*-secenek-1,aria-labelledby=*-baslik]","option[aria-selected=false]","option[aria-selected=false]","option[aria-disabled=true,aria-selected=false]","option[aria-selected=false]"]
öge adları: ["div","button","ul","li","li","li","li"] / ["nav","h2","ul","li","li","li","li"]
```

İzleme tablosu davranışın tamamını on satırda gösteriyor. Üçüncü ok tuşunda etkin dizin
1'den 3'e atlıyor: kapalı öğe atlanmış. `Home` tuşu başa gidiyor, oradan yukarı ok yine
kapalı öğeyi atlayarak sona sarıyor. `Enter` seçimi yapıp listeyi kapatıyor; ardından gelen
ok tuşu listeyi seçili öğe etkin olacak biçimde açıyor. Son satırda `Escape` listeyi
kapatıyor ama seçimi bozmuyor.

İki görünümün rol imzaları öğe düzeyinde birebir aynı: dört seçenek, aynı seçim durumları,
aynı kapalılık bildirimi, aynı etkin öğe. Ayrım yalnızca kapsayan yapıda — biri düğmeli bir
açılır liste, diğeri başlığa bağlanmış bir yan panel. Öge adları da farklı. Davranış aynı
kaldığı hâlde görünüm bağımsız değişebiliyor.

#### Davranışın Görünümsüz Sınanması

Başsız bileşenin en somut kazancı burada görünür: davranış, hiçbir görünüm kurulmadan
sınanır.

```js
// bassiz.test.mjs — davranisin sinanmasi; hicbir gorunum, hicbir DOM kurulmuyor
import { test } from "node:test";
import { strictEqual, deepStrictEqual } from "node:assert";
import { SECENEKLER, baslangic, indirge, aria } from "./bassiz.mjs";

const oynat = (olaylar, durum = baslangic()) => olaylar.reduce((d, o) => indirge(d, o), durum);
const tus = (t) => ({ t: "tus", tus: t });

test("ok tuşu pasif seçeneği atlar", () => {
  const d = oynat([tus("ArrowDown"), tus("ArrowDown"), tus("ArrowDown")]);
  strictEqual(SECENEKLER[d.etkin].deger, "zirve");
});

test("son seçenekten sonra başa döner, başta yukarı sona gider", () => {
  const son = oynat([tus("End")], { ...baslangic(), acik: true });
  strictEqual(son.etkin, 3);
  strictEqual(oynat([tus("ArrowDown")], son).etkin, 0);
  strictEqual(oynat([tus("ArrowUp")], { ...son, etkin: 0 }).etkin, 3);
});

test("Enter seçer ve kapatır, Escape seçimi değiştirmez", () => {
  const secilmis = oynat([tus("ArrowDown"), tus("ArrowDown"), tus("Enter")]);
  strictEqual(secilmis.secili, "orta");
  strictEqual(secilmis.acik, false);
  const kacilmis = oynat([tus("ArrowDown"), tus("Escape")], secilmis);
  strictEqual(kacilmis.secili, "orta");
  strictEqual(kacilmis.acik, false);
});

test("açılışta seçili olan seçenek etkin olur", () => {
  strictEqual(oynat([{ t: "ac" }], baslangic("zirve")).etkin, 3);
  strictEqual(oynat([{ t: "ac" }], baslangic(null)).etkin, 0);
});

test("harf arabelleği 500 ms içinde birikir, sonra sıfırlanır", () => {
  const a = oynat([{ t: "harf", harf: "o", zaman: 1000 }]);
  strictEqual(SECENEKLER[a.etkin].deger, "orta");
  const b = indirge(a, { t: "harf", harf: "r", zaman: 1200 });   // "or" -> Orta teras
  strictEqual(b.arabellek, "or");
  const c = indirge(b, { t: "harf", harf: "z", zaman: 2000 });   // arabellek sifirlandi
  strictEqual(c.arabellek, "z");
  strictEqual(SECENEKLER[c.etkin].deger, "zirve");
});

test("pasif seçenek işaretlenemez ve seçilemez", () => {
  const acik = oynat([{ t: "ac" }]);
  strictEqual(indirge(acik, { t: "isaretle", dizin: 2 }).etkin, acik.etkin);
  const zorlanmis = { ...acik, etkin: 2 };
  strictEqual(indirge(zorlanmis, tus("Enter")).secili, null);
});

test("indirgeyici verilen durumu değiştirmez", () => {
  const once = oynat([{ t: "ac" }]);
  const kopya = structuredClone(once);
  indirge(once, tus("ArrowDown"));
  deepStrictEqual(once, kopya);
});

test("etkin betimleyici yalnızca liste açıkken bildirilir", () => {
  const kapali = baslangic("orta");
  strictEqual(aria(kapali, "k").liste["aria-activedescendant"], "");
  strictEqual(aria(kapali, "k").tetikleyici["aria-expanded"], "false");
  const acik = indirge(kapali, { t: "ac" });
  strictEqual(aria(acik, "k").liste["aria-activedescendant"], "k-secenek-1");
  strictEqual(aria(acik, "k").secenek(1)["aria-selected"], "true");
  strictEqual(aria(acik, "k").secenek(2)["aria-disabled"], "true");
});
```

Dosya `node --test bassiz.test.mjs` ile çalıştırılır. Aşağıdaki çıktıda süre değerleri
makineye göre değişir; sınama sonuçları değişmez.

{/* mask: (?<=\()[0-9.]+(?=ms\)) */}
{/* mask: (?<=duration_ms )[0-9.]+ */}
```
✔ ok tuşu pasif seçeneği atlar (0.43125ms)
✔ son seçenekten sonra başa döner, başta yukarı sona gider (0.064375ms)
✔ Enter seçer ve kapatır, Escape seçimi değiştirmez (0.05875ms)
✔ açılışta seçili olan seçenek etkin olur (0.349ms)
✔ harf arabelleği 500 ms içinde birikir, sonra sıfırlanır (0.123ms)
✔ pasif seçenek işaretlenemez ve seçilemez (0.059625ms)
✔ indirgeyici verilen durumu değiştirmez (0.343333ms)
✔ etkin betimleyici yalnızca liste açıkken bildirilir (0.093542ms)
ℹ tests 8
ℹ suites 0
ℹ pass 8
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 40.331
```

Sekiz sınama, hiçbir tarayıcı ve hiçbir sahte DOM olmadan çalışıyor. Harfle arama sınaması
zamanı bir sayı olarak veriyor; gerçek bir zamanlayıcı beklenmediği için sınama
milisaniyeler yerine mikrosaniyeler sürüyor. Yedinci sınama durumun değiştirilemezliğini
denetliyor: indirgeyici verilen nesneyi değiştirirse, çerçevenin değişikliği saptama
düzeneği de bozulur.

#### Başsız Bileşenin Sınırları

Başsız yaklaşımın bedeli, görünümü kuran tarafın işinin artmasıdır. Öznitelikleri
işaretlemeye takmak, olayları doğru öğeye bağlamak ve etkin öğeyi görünür alana kaydırmak
çağıranın sorumluluğuna geçer. Bu iş her kullanımda tekrar edilir ve bir yerde eksik
yapılır.

Sınır şuradan geçer: davranış birden çok görünümle kullanılacaksa başsız yazmak
karşılığını verir; tek bir görünümle kullanılacaksa aynı davranışı bir bileşenin içinde
tutmak daha az yüzey demektir. Ara bir yol, başsız çekirdeğin üzerine varsayılan bir görünüm
koymaktır: yaygın durumda hazır bileşen kullanılır, gerektiğinde çekirdeğe inilir.

İkinci sınır, bazı davranışların saf kalamamasıdır. Etkin öğenin görünür alana kaydırılması,
açılır listenin ekran kenarına göre konumlandırılması ve odağın liste kapanınca tetikleyiciye
dönmesi ölçüm ve DOM erişimi gerektirir. Bunlar durum makinesinin dışında, ayrı ve açıkça
işaretlenmiş bir katmanda durur; makinenin saflığı korunur.

#### Özet

- Başsız bileşen davranışı ve erişilebilirlik sözleşmesini taşır, görünüm üretmez; dışarıya
  okunacak bir durum ve işaretlemeye takılacak öznitelikler verir.
- Davranışın taşıyıcısı saf bir indirgeyicidir; zaman bile olayın içinde verilir, böylece
  sınama zamandan bağımsız kalır.
- Erişilebilirlik öznitelikleri durumun türevidir; etkin öğe odağı almaz, kimliği
  `aria-activedescendant` ile bildirilir.
- Aynı durumdan iki farklı görünüm üretildiğinde rol ve durum imzaları aynı kalır, öge
  adları ve kapsayan yapı değişir.
- Davranış hiçbir görünüm kurulmadan olay dizileriyle sınanır; indirgeyicinin durumu
  değiştirmemesi de sınanacak bir özelliktir.
- Ölçüm ve odak taşıma gerektiren davranışlar makinenin dışında, ayrı bir katmanda kalır;
  başsız yaklaşım tek görünümlü kullanımda karşılığını vermez.

#### Sonraki Adım

Bu konuda kurulan her şey bileşenin dış yüzeyine dayanıyordu: hangi özellikleri aldığı,
hangi değerleri verdiği, hangi deliği bıraktığı. Bu yüzeyin kendisi henüz tasarlanmadı.
Çağıran bileşene tanımadığı bir öznitelik verdiğinde ne olacak? Bileşenin kendi sınıf adı ile
çağıranın verdiği sınıf adı çakıştığında hangisi kazanacak? Bileşen bir olayı hem kendisi
dinleyip hem çağıranın dinleyicisini çağırabilecek mi? Değerin sahibi bileşen mi, çağıran
mı? Sonraki ders bu soruların yanıtını özellik yayılması, varsayılan birleştirmesi ve
denetim sahipliği başlıkları altında toplar.

### Bileşen API Tasarımı

Source: https://academia.sh/tr/kurslar/bilesen-tabanli-gelistirme/bilesen-api-tasarimi

Özellik yayılmasının birleştirme kuralları, sınıf ve dinleyicilerin birikmeli birleşimi, korunan alanlar, denetimli ile denetimsiz kip arasındaki sahiplik kararı ve özellik yüzeyinin geriye dönük uyumluluğu.

Bu konuda kurulan her şey bileşenin dış yüzeyine dayanıyordu: hangi özellikleri aldığı,
hangi deliği bıraktığı, hangi tutamağı verdiği. Yüzeyin kendisi henüz tasarlanmadı.

Dört soru açık duruyor. Çağıran bileşene tanımadığı bir öznitelik verdiğinde ne olacak?
Bileşenin kendi sınıf adıyla çağıranın verdiği sınıf adı çakışırsa hangisi kazanacak?
Bileşen bir olayı hem kendisi dinleyip hem çağıranın dinleyicisini çağırabilecek mi? Ve
gösterilen değerin sahibi bileşen mi, çağıran mı?

#### Yayma Sırası

Bir bileşen, tanıdığı özellikleri kendi işine ayırıp geri kalanı ürettiği köke geçirebilir.
Buna **özellik yayılması (prop spreading)** denir ve çağırana bir kaçış deliği verir:
bileşenin düşünmediği bir öznitelik, bileşen değiştirilmeden eklenebilir.

Yayma tek başına yapıldığında sonuç, yazım sırasına bağlıdır ve birikmeli alanlar kaybolur.

```js
// api.mjs — ozellik yayilmasi, birlestirme kurallari ve yayma sirasi
const oge = (ad, ozn = {}, ...cocuklar) => ({ ad, ozn, cocuklar });

// Olayin kucuk modeli: iptal edilebilir varsayilan.
const olayYap = (tur) => ({ tur, varsayilanEngellendi: false, engelle() { this.varsayilanEngellendi = true; } });

// Iki islevi zincirle: once disaridan gelen, varsayilan engellenmediyse icteki.
const zincirle = (once, sonra) => (olay) => {
  once?.(olay);
  if (!olay.varsayilanEngellendi) sonra?.(olay);
};

// Birlestirme kurali: siniflar eklenir, bicem anahtar anahtar birlesir, duz oznitelikte
// sonuncu kazanir, dinleyicide ise SONUNCU ONCE calisir (cagiran bileseni iptal edebilsin).
function birlestir(...kumeler) {
  const sonuc = {};
  for (const kume of kumeler) {
    for (const [ad, deger] of Object.entries(kume ?? {})) {
      if (deger === undefined) continue;
      if (ad === "class") sonuc.class = [sonuc.class, deger].filter(Boolean).join(" ");
      else if (ad === "style") sonuc.style = { ...(sonuc.style ?? {}), ...deger };
      else if (/^on[A-Z]/.test(ad) && typeof deger === "function") {
        const onceki = sonuc[ad];
        sonuc[ad] = onceki ? zincirle(deger, onceki) : deger;
      } else sonuc[ad] = deger;
    }
  }
  return sonuc;
}

const kutuk = [];
const gelen = {
  class: "genis",
  style: { marginTop: "8px", color: "crimson" },
  onClick: () => kutuk.push("çağıranın dinleyicisi"),
  "data-izleme": "rozet-1",
  type: "submit",
};
const kendi = {
  class: "rozet-dugme",
  style: { color: "inherit", padding: "4px 8px" },
  onClick: () => kutuk.push("bileşenin kendi dinleyicisi"),
  type: "button",
};

// (a) Duz yayma: sonuncu kazanir, birikmeli alanlar kaybolur.
const duzOnce = { ...kendi, ...gelen };
const duzSonra = { ...gelen, ...kendi };
console.log("düz yayma, çağıran sonra :", JSON.stringify({ class: duzOnce.class, style: duzOnce.style, type: duzOnce.type }));
console.log("düz yayma, bileşen sonra :", JSON.stringify({ class: duzSonra.class, style: duzSonra.style, type: duzSonra.type }));

// (b) Birlestirme.
const birlesik = birlestir(kendi, gelen);
console.log("birleştirme              :", JSON.stringify({ class: birlesik.class, style: birlesik.style, type: birlesik.type }));

kutuk.length = 0;
duzOnce.onClick(olayYap("click"));
console.log("\ndüz yaymada çalışan dinleyiciler   :", JSON.stringify(kutuk));
kutuk.length = 0;
birlesik.onClick(olayYap("click"));
console.log("birleştirmede çalışan dinleyiciler:", JSON.stringify(kutuk));

// Cagiran varsayilani engellerse bilesenin dinleyicisi calismaz.
kutuk.length = 0;
const iptalEden = birlestir(kendi, { ...gelen, onClick: (o) => { kutuk.push("çağıran: iptal"); o.engelle(); } });
iptalEden.onClick(olayYap("click"));
console.log("çağıran iptal ettiğinde           :", JSON.stringify(kutuk));

// (c) Korunan alanlar: bilesenin dogrulugu tasiyan ozniteliklerini cagiran ezemez.
const KORUNAN = new Set(["role", "aria-controls", "id"]);
function guvenliBirlestir(kendi, gelen) {
  const suzulmus = Object.fromEntries(Object.entries(gelen).filter(([a]) => !KORUNAN.has(a)));
  const dusen = Object.keys(gelen).filter((a) => KORUNAN.has(a));
  return { ozellikler: birlestir(kendi, suzulmus), dusen };
}
const { ozellikler, dusen } = guvenliBirlestir(
  { role: "tab", id: "suzgec-baslik-esik", class: "sekme", "aria-controls": "suzgec-govde-esik" },
  { role: "button", id: "benim-kimligim", "aria-controls": "baska-govde", class: "vurgulu", title: "Eşik ayarları" }
);
console.log("\nkorunan alanlar düşürüldü:", JSON.stringify(dusen));
console.log("sonuç:", JSON.stringify(ozellikler));
console.log("üretilen düğüm:", JSON.stringify(oge("button", ozellikler).ozn));
```

```
düz yayma, çağıran sonra : {"class":"genis","style":{"marginTop":"8px","color":"crimson"},"type":"submit"}
düz yayma, bileşen sonra : {"class":"rozet-dugme","style":{"color":"inherit","padding":"4px 8px"},"type":"button"}
birleştirme              : {"class":"rozet-dugme genis","style":{"color":"crimson","padding":"4px 8px","marginTop":"8px"},"type":"submit"}

düz yaymada çalışan dinleyiciler   : ["çağıranın dinleyicisi"]
birleştirmede çalışan dinleyiciler: ["çağıranın dinleyicisi","bileşenin kendi dinleyicisi"]
çağıran iptal ettiğinde           : ["çağıran: iptal"]

korunan alanlar düşürüldü: ["role","id","aria-controls"]
sonuç: {"role":"tab","id":"suzgec-baslik-esik","class":"sekme vurgulu","aria-controls":"suzgec-govde-esik","title":"Eşik ayarları"}
üretilen düğüm: {"role":"tab","id":"suzgec-baslik-esik","class":"sekme vurgulu","aria-controls":"suzgec-govde-esik","title":"Eşik ayarları"}
```

İlk iki satır iki yönlü kaybı gösteriyor. Çağıran sonra yayıldığında bileşenin kendi
sınıfı ve `type` değeri siliniyor; düğme, çağıranın hiç istemediği bir gönderim düğmesine
dönüşüyor. Bileşen sonra yayıldığında bu kez çağıranın verdiği hiçbir şey kalmıyor ve kaçış
deliği kapanıyor.

Üçüncü satır birleştirmenin sonucunu veriyor. Üç alan üç ayrı kuralla işleniyor. Sınıf
**eklenir**: bileşenin sınıfı önde, çağıranınki arkada; CSS basamaklamasında sıra
belirleyici olmadığı için ikisi de kuralda kalır. Biçem **anahtar anahtar birleşir**:
çağıranın verdiği `color` bileşeninkini eziyor, `padding` bileşenin verdiği gibi kalıyor,
`marginTop` yalnızca çağıranda var. Düz öznitelikte **sonuncu kazanır**: `type` çağıranın
istediği gibi.

#### Dinleyicilerin Zincirlenmesi

Dinleyiciler için tek bir değer seçmek her zaman yanlıştır: bileşenin kendi davranışı da,
çağıranın isteği de çalışmalıdır. Çıktının ikinci bölümü düz yaymada bileşenin dinleyicisinin
tümüyle kaybolduğunu, birleştirmede ise ikisinin de çalıştığını gösteriyor.

Zincirin **sırası** bir tasarım kararıdır ve düz özniteliklerin kuralının tersidir. Çağıranın
dinleyicisi önce çalışır; varsayılanı engellerse bileşenin kendi davranışı hiç çalışmaz. Son
satır bunu gösteriyor: yalnızca çağıranın dinleyicisi çalışmış, bileşeninki atlanmış. Ters
sıra seçilseydi çağıran bileşeni durduramazdı, yalnızca ondan sonra bir şey ekleyebilirdi.

Tarayıcı ve Web Platformu kursundaki Varsayılan Davranış ve İptal dersinde tanımlanan
iptal edilebilirlik burada bileşen düzeyine taşınmış oluyor: bileşenin davranışı, çağıranın
iptal edebileceği bir varsayılan gibi ele alınıyor.

#### Korunan Alanlar

Kaçış deliği sınırsız olamaz. Çıktının son bölümünde çağıran `role`, `id` ve `aria-controls`
değerlerini değiştirmeye çalışıyor; üçü de düşürülüyor.

Ayrım, ilk derste kurulan ölçütle aynıdır. `title` bir görünüm kararıdır ve geçer. `role`
ise bileşenin doğruluğunun parçasıdır: bir sekme başlığının rolü değiştirilirse başlıkla
gövde arasındaki ilişki anlamını yitirir. `aria-controls` ve `id` de bileşenin kendi
ürettiği ilişkiyi taşır; çağıranın verdiği değer bu ilişkiyi koparır.

Korunan alanların **sessizce** düşürülmesi de doğru değildir; geliştirme kipinde bir uyarı
yazılması, çağıranın kaçış deliğinin nerede bittiğini öğrenmesini sağlar.

#### Değerin Sahibi

Son soru en sık kusur üretenidir: bileşenin gösterdiği değeri kim tutar?

```js
// denetim.mjs — degerin sahibi kim: denetimli ve denetimsiz kip
function alanOrnegi() {
  let icDurum;
  let kip = null;
  const uyarilar = [];
  return {
    uyarilar,
    ciz({ deger, ilkDeger = -5, degisti }) {
      const denetimli = deger !== undefined;
      if (kip === null) { kip = denetimli ? "denetimli" : "denetimsiz"; if (!denetimli) icDurum = ilkDeger; }
      else if ((kip === "denetimli") !== denetimli) {
        uyarilar.push(`kip değişti: ${kip} → ${denetimli ? "denetimli" : "denetimsiz"}`);
        kip = denetimli ? "denetimli" : "denetimsiz";
        if (!denetimli) icDurum = ilkDeger;
      }
      const gosterilen = denetimli ? deger : icDurum;
      return {
        kip, gosterilen,
        yaz(yeni) { if (!denetimli) icDurum = yeni; degisti?.(yeni); },
      };
    },
  };
}

const satir = (etiket, g) => console.log(`${etiket.padEnd(34)} ${String(g).padStart(6)}`);

console.log("--- A. denetimsiz: deger bilesenin ---");
const a = alanOrnegi();
let g = a.ciz({ ilkDeger: -5 });
satir("ilk çizim (ilkDeger=-5)", g.gosterilen);
g.yaz(12);
g = a.ciz({ ilkDeger: -5 });
satir("kullanıcı 12 yazdıktan sonra", g.gosterilen);
g = a.ciz({ ilkDeger: 20 });
satir("ilkDeger 20 olarak verildiğinde", g.gosterilen);

console.log("\n--- B. denetimli: deger cagiranin, degisti bagli ---");
const b = alanOrnegi();
let ustDurum = -5;
const bCiz = () => b.ciz({ deger: ustDurum, degisti: (v) => { ustDurum = v; } });
g = bCiz();
satir("ilk çizim (deger=-5)", g.gosterilen);
g.yaz(12);
g = bCiz();
satir("kullanıcı 12 yazdıktan sonra", g.gosterilen);
ustDurum = 30;
g = bCiz();
satir("üst durum 30'a çekildiğinde", g.gosterilen);

console.log("\n--- C. denetimli ama degisti yok: donmus alan ---");
const c = alanOrnegi();
const cCiz = () => c.ciz({ deger: -5 });
g = cCiz();
satir("ilk çizim (deger=-5)", g.gosterilen);
g.yaz(12);
g = cCiz();
satir("kullanıcı 12 yazdıktan sonra", g.gosterilen);

console.log("\n--- D. kip degisimi ---");
const d = alanOrnegi();
d.ciz({ ilkDeger: -5 });
d.ciz({ deger: 7, degisti: () => {} });
d.ciz({ ilkDeger: -5 });
console.log("uyarılar:", JSON.stringify(d.uyarilar));

// Iki kipi tek imzada barindiran bilesenin sozlesmesi
console.log("\nözellik durumu             kip          değeri kim tutar  değişimi kim bildirir");
for (const [deger, degisti] of [[undefined, undefined], [undefined, "var"], [-5, undefined], [-5, "var"]]) {
  const denetimli = deger !== undefined;
  console.log(
    `${(`deger=${deger ?? "yok"}, değişti=${degisti ?? "yok"}`).padEnd(26)} ` +
    `${(denetimli ? "denetimli" : "denetimsiz").padEnd(12)} ` +
    `${(denetimli ? "çağıran" : "bileşen").padEnd(17)} ` +
    `${degisti ? "çağıran dinler" : denetimli ? "kimse — değer donar" : "kimse"}`
  );
}
```

```
--- A. denetimsiz: deger bilesenin ---
ilk çizim (ilkDeger=-5)                -5
kullanıcı 12 yazdıktan sonra           12
ilkDeger 20 olarak verildiğinde        12

--- B. denetimli: deger cagiranin, degisti bagli ---
ilk çizim (deger=-5)                   -5
kullanıcı 12 yazdıktan sonra           12
üst durum 30'a çekildiğinde            30

--- C. denetimli ama degisti yok: donmus alan ---
ilk çizim (deger=-5)                   -5
kullanıcı 12 yazdıktan sonra           -5

--- D. kip degisimi ---
uyarılar: ["kip değişti: denetimsiz → denetimli","kip değişti: denetimli → denetimsiz"]

özellik durumu             kip          değeri kim tutar  değişimi kim bildirir
deger=yok, değişti=yok     denetimsiz   bileşen           kimse
deger=yok, değişti=var     denetimsiz   bileşen           çağıran dinler
deger=-5, değişti=yok      denetimli    çağıran           kimse — değer donar
deger=-5, değişti=var      denetimli    çağıran           çağıran dinler
```

**Denetimsiz kip** değeri bileşene bırakır. Çağıran yalnızca başlangıcı verir; sonrası
bileşenin işidir. A bölümünün son satırı bu kipin en çok şaşırtan yanını gösteriyor:
`ilkDeger` 20 yapıldığı hâlde gösterilen değer 12 kalıyor. "İlk" sözcüğü sözleşmenin
kendisidir — özellik yalnızca ilk çizimde okunur. Adında "ilk" geçmeyen bir özellik bu
davranışı taşıyamaz.

**Denetimli kip** değeri çağırana bırakır. B bölümünde bileşen hiçbir değer tutmuyor;
gösterilen şey her zaman gelen özelliktir. Üst durum bileşenden bağımsız değiştiğinde
görünüm de değişiyor — denetimli kipi seçmenin nedeni budur, çünkü değerin başka bir yerden
de değişmesi gerekebilir.

C bölümü tek bir eksiğin ne yaptığını gösteriyor. Değer verilmiş, bildirim işlevi
verilmemiş; kullanıcı yazıyor, hiçbir şey olmuyor. Alan donmuş görünüyor ve bunun nedeni
kaynak metinde bir satır uzağa yazılmış. Bileşen bu birleşimi geliştirme kipinde
bildirmelidir.

D bölümü ikinci tuzağı gösteriyor: aynı bileşen örneği kip değiştirdiğinde, değerin sahibi
ömrün ortasında değişir. Bu, genellikle üst durumun ilk çizimde tanımsız olmasından doğar.
Sözleşme, kipin örneğin ömrü boyunca sabit olmasını ister.

#### Yüzeyin Kararlılığı

Bileşenin özellik yüzeyi bir sözleşmedir ve sözleşme değişiklikleri iki sınıfa ayrılır.

Varsayılanı olan yeni bir özellik eklemek uyumludur; hiçbir çağıran etkilenmez. Var olan bir
özelliğin varsayılanını değiştirmek uyumlu **değildir**: kaynak metnini hiç değiştirmemiş
her çağıran farklı davranış görür. Bir özelliğin adını değiştirmek de uyumsuzdur, ama en
azından gürültülüdür — eski ad artık okunmaz ve etkisi hemen görülür.

Yüzeyin biçimi de kararlılığı etkiler. İlk derste görüldüğü gibi bağımsız mantıksal
özellikler üstel bir birleşim kümesi üretir; birbirini dışlayan seçenekler tek bir
adlandırılmış özellikte toplandığında hem küme küçülür hem de yeni bir seçenek eklemek
uyumlu bir değişiklik olur. Aynı ölçüt geri bildirim işlevleri için de geçerlidir: tek
değer alan bir işlev yerine adlandırılmış alanları olan bir nesne almak, ileride alan
eklenmesine izin verir.

#### Özet

- Özellik yayılması çağırana kaçış deliği verir; düz yayma ise yazım sırasına göre ya
  bileşenin ya çağıranın verdiklerini siler.
- Birleştirmenin üç kuralı vardır: sınıf eklenir, biçem anahtar anahtar birleşir, düz
  öznitelikte sonuncu kazanır.
- Dinleyiciler zincirlenir ve zincirin sırası düz özniteliklerin tersidir: çağıranın
  dinleyicisi önce çalışır, varsayılanı engellerse bileşenin davranışı çalışmaz.
- Bileşenin doğruluğunu taşıyan alanlar korunur; düşürülen alan geliştirme kipinde
  bildirilir.
- Denetimsiz kipte değer bileşenin, denetimli kipte çağıranındır; "ilk" ön eki taşıyan
  özellik yalnızca ilk çizimde okunur, kip örneğin ömrü boyunca sabit kalmalıdır ve değer
  verilip bildirim işlevi verilmemesi alanı dondurur.
- Varsayılanı olan yeni özellik eklemek uyumlu, var olan varsayılanı değiştirmek uyumsuz
  bir değişikliktir.

#### Sonraki Adım

Bu konu boyunca bileşenler kâğıt üzerinde birleştirildi: bir işlev bir ağaç üretti, ağaçlar
iç içe geçti, özellikler birleşti. Üretilen ağacın belgeye nasıl yansıdığına hiç
bakılmadı — durum değiştiğinde hangi düğümlerin dokunulduğu, ne kadar karşılaştırma
yapıldığı ve bu işin kimin yaptığı açık bırakıldı. Bu soruların yanıtı bileşim kalıplarına
değil, çerçevenin güncelleme modeline bağlıdır ve aileden aileye değişir. Sonraki konu, bu
modelleri özellikleriyle ele alır ve ilk ailede, durumdan yeni bir ağaç üretip onu
öncekiyle karşılaştıran yaklaşımda, bir hücrelik değişikliğin kaç birim iş ürettiğini sayar.

## Çerçeve Aileleri

Sanal ağaç tutan, çalışma zamanında bağımlılık izleyen, derleme zamanında tepkiselleştiren ve bütünleşik sözleşme sunan çerçeve ailelerinin güncelleme modelleri, maliyetleri ve seçim ölçütleri.

### Sanal Ağaç Tabanlı Çerçeveler

Source: https://academia.sh/tr/kurslar/bilesen-tabanli-gelistirme/sanal-agac-tabanli-cerceveler

Durumdan yeni bir ağaç üretip öncekiyle karşılaştıran güncelleme modeli; bir hücrelik değişikliğin ürettiği iş miktarının sayılması, anahtarın uzlaşmadaki rolü ve bellemenin ödünleşimi.

Önceki konu boyunca bileşenler kâğıt üzerinde birleştirildi: bir işlev bir ağaç üretti,
ağaçlar iç içe geçti, özellikler birleşti. Üretilen ağacın belgeye nasıl yansıdığına hiç
bakılmadı.

Bu soruya tek bir yanıt yoktur. Çerçeveler, durum değiştiğinde belgenin hangi bölümünün
güncelleneceğini bulmak için birbirinden ayrı yollar izler ve bu yol, çerçevenin öğrenme
eğrisinden başarım profiline kadar her şeyini belirler. Bu konu dört yolu özellikleriyle
ele alır. İlki, güncellemeyi bir **karşılaştırma** problemi olarak gören ailedir.

#### Yeniden Çizim Birimi Bileşendir

Modelin adımları şöyledir. Bir bileşenin durumu değişir. Çerçeve o bileşenin işlevini
yeniden çalıştırır; işlev, güncel durumdan yeni bir ağaç üretir. Bu ağaç belge değildir —
belgeyi tarif eden bellek içi bir yapıdır ve **sanal ağaç** adıyla anılır. Çerçeve yeni
ağacı bir önceki ağaçla karşılaştırır, farkları bulur ve yalnızca farkları belgeye yazar.

Modelin kazancı, bileşen yazan kişinin belgeye hiç dokunmamasıdır. Bildirimsel Oluşturma
dersindeki tek yönlü akış burada tam anlamıyla geçerlidir: durumdan görünüme bir işlev
vardır, tersi yoktur.

Bedeli ise şu cümlede saklıdır: **yeniden çizim birimi bileşendir.** Bir bileşenin
durumundaki değişiklik, o bileşenin ürettiği ağacın tamamının yeniden üretilmesine ve
karşılaştırılmasına yol açar; değişen bölüm ne kadar küçük olursa olsun.

#### Bir Hücrelik Değişikliğin Maliyeti

İstasyon sayfasındaki ölçüm tablosunda iki yüz satır var. Tek bir sensörün değeri
güncelleniyor. Bunun ürettiği iş sayılabilir.

```js
// sanal-agac.mjs — yeniden cizim, fark alma ve bir hucrelik degisikligin maliyeti
let sayac;
const sifirla = () => (sayac = { bilesen: 0, dugum: 0, oznitelik: 0, yama: 0, atlanan: 0 });

const h = (ad, ozn = {}, ...cocuklar) => ({ ad, ozn, cocuklar: cocuklar.flat().filter((c) => c != null) });
const yazi = (d) => ({ ad: "#metin", ozn: {}, cocuklar: [], metin: String(d) });

// --- Bilesenler: her cizimde yeniden calisir ve yeni bir agac uretir ---
const Hucre = ({ deger }) => { sayac.bilesen++; return h("td", { class: "deger" }, yazi(`${deger.toFixed(1)} °C`)); };
const Satir = ({ ad, deger }) => {
  sayac.bilesen++;
  return h("tr", { anahtar: ad }, h("th", { scope: "row" }, yazi(ad)), Hucre({ deger }));
};
const Tablo = ({ satirlar, ciz }) => {
  sayac.bilesen++;
  return h("table", { class: "olcum" },
    h("caption", {}, yazi("Kuzey Yamaç")),
    h("tbody", {}, ...satirlar.map(ciz)));
};

// --- Belleme: ozellikleri degismeyen bilesen onceki agacini aynen dondurur ---
function belle(bilesen) {
  const onbellek = new Map();
  return (o) => {
    const anahtar = o.ad;
    const kayit = onbellek.get(anahtar);
    if (kayit && Object.keys(o).every((a) => kayit.ozellikler[a] === o[a])) { sayac.atlanan++; return kayit.agac; }
    const agac = bilesen(o);
    onbellek.set(anahtar, { ozellikler: { ...o }, agac });
    return agac;
  };
}

// --- Fark alma ---
function fark(eski, yeni) {
  if (eski === yeni) { sayac.dugum++; return; }        // ayni basvuru: alt agaca hic inilmez
  sayac.dugum++;
  if (eski.ad !== yeni.ad) { sayac.yama++; return; }
  if (eski.metin !== undefined || yeni.metin !== undefined) {
    if (eski.metin !== yeni.metin) sayac.yama++;
    return;
  }
  for (const a of new Set([...Object.keys(eski.ozn), ...Object.keys(yeni.ozn)])) {
    sayac.oznitelik++;
    if (eski.ozn[a] !== yeni.ozn[a]) sayac.yama++;
  }
  const anahtarli = yeni.cocuklar.length > 0 && yeni.cocuklar.every((c) => c.ozn?.anahtar !== undefined);
  if (!anahtarli) {
    const n = Math.max(eski.cocuklar.length, yeni.cocuklar.length);
    for (let i = 0; i < n; i++) {
      if (!eski.cocuklar[i] || !yeni.cocuklar[i]) { sayac.yama++; continue; }
      fark(eski.cocuklar[i], yeni.cocuklar[i]);
    }
    return;
  }
  const eskiHarita = new Map(eski.cocuklar.map((c, i) => [c.ozn.anahtar, { dugum: c, sira: i }]));
  const eskiSira = yeni.cocuklar.map((c) => eskiHarita.get(c.ozn.anahtar)?.sira ?? -1);
  const yerindeKalan = enUzunArtanAltDizi(eskiSira);   // tasinmasi gerekmeyen dugumler
  yeni.cocuklar.forEach((c, i) => {
    const bulunan = eskiHarita.get(c.ozn.anahtar);
    if (!bulunan) { sayac.yama++; return; }            // yeni dugum: eklenir
    if (!yerindeKalan.has(i)) sayac.yama++;            // en az sayida tasima
    fark(bulunan.dugum, c);
    eskiHarita.delete(c.ozn.anahtar);
  });
  sayac.yama += eskiHarita.size;                       // kalanlar silinir
}

// En uzun artan alt dizi: tasinmasi gerekmeyen dugumlerin dizinlerini verir.
function enUzunArtanAltDizi(dizi) {
  const oncul = new Array(dizi.length).fill(-1);
  const kuyruk = [];
  for (let i = 0; i < dizi.length; i++) {
    if (dizi[i] < 0) continue;
    let alt = 0, ust = kuyruk.length;
    while (alt < ust) { const orta = (alt + ust) >> 1; if (dizi[kuyruk[orta]] < dizi[i]) alt = orta + 1; else ust = orta; }
    if (alt > 0) oncul[i] = kuyruk[alt - 1];
    kuyruk[alt] = i;
  }
  const sonuc = new Set();
  for (let k = kuyruk[kuyruk.length - 1]; k !== undefined && k !== -1; k = oncul[k]) sonuc.add(k);
  return sonuc;
}

const raporla = (baslik) =>
  console.log(`${baslik.padEnd(36)} ${String(sayac.bilesen).padStart(9)} ${String(sayac.atlanan).padStart(8)} ` +
    `${String(sayac.dugum).padStart(9)} ${String(sayac.oznitelik).padStart(11)} ${String(sayac.yama).padStart(5)}`);

const SATIR_SAYISI = 200;
const veri = (kaydirma = 0) =>
  Array.from({ length: SATIR_SAYISI }, (_, i) => ({ ad: `sensör-${String(i).padStart(3, "0")}`, deger: -10 + i * 0.1 + kaydirma }));

console.log("senaryo                              bileşen  atlanan     düğüm    öznitelik  yama");

// 1. Belleme yok: tek hucre degisiyor.
sifirla();
const v1 = veri();
const eski1 = Tablo({ satirlar: v1, ciz: Satir });
const v2 = v1.map((s, i) => (i === 120 ? { ...s, deger: 42.5 } : s));
const yeni1 = Tablo({ satirlar: v2, ciz: Satir });
fark(eski1, yeni1);
raporla("bellemesiz, tek hücre değişti");

// 2. Belleme var: ozellikleri degismeyen satirlar atlanir.
sifirla();
const bellenmisSatir = belle(Satir);
const eski2 = Tablo({ satirlar: v1, ciz: bellenmisSatir });
const yeni2 = Tablo({ satirlar: v2, ciz: bellenmisSatir });
fark(eski2, yeni2);
raporla("bellemeli, tek hücre değişti");

// 3. Hicbir sey degismedi (ust bilesen baska bir nedenle cizildi).
sifirla();
const eski3 = Tablo({ satirlar: v1, ciz: Satir });
const yeni3 = Tablo({ satirlar: v1, ciz: Satir });
fark(eski3, yeni3);
raporla("bellemesiz, hiçbir şey değişmedi");

// 4. Basa bir satir eklendi — anahtarli uzlasma.
sifirla();
const yeniSatir = { ad: "sensör-yeni", deger: 3.3 };
const eski4 = Tablo({ satirlar: v1, ciz: Satir });
const yeni4 = Tablo({ satirlar: [yeniSatir, ...v1], ciz: Satir });
fark(eski4, yeni4);
raporla("başa satır eklendi (anahtarlı)");

// 5. Ayni ekleme, anahtar yokken: her satir kaydigi icin karsilastirma eslesmiyor.
sifirla();
const SatirAnahtarsiz = ({ ad, deger }) => {
  sayac.bilesen++;
  return h("tr", {}, h("th", { scope: "row" }, yazi(ad)), Hucre({ deger }));
};
const eski5 = Tablo({ satirlar: v1, ciz: SatirAnahtarsiz });
const yeni5 = Tablo({ satirlar: [yeniSatir, ...v1], ciz: SatirAnahtarsiz });
fark(eski5, yeni5);
raporla("başa satır eklendi (anahtarsız)");
```

```
senaryo                              bileşen  atlanan     düğüm    öznitelik  yama
bellemesiz, tek hücre değişti              802        0      1004         601     1
bellemeli, tek hücre değişti               404      199       208           4     1
bellemesiz, hiçbir şey değişmedi           802        0      1004         601     0
başa satır eklendi (anahtarlı)             804        0      1004         601     1
başa satır eklendi (anahtarsız)            804        0      1004         401   401
```

#### Sayıların Okunması

İlk satır modelin özetidir. Belgeye yazılan tek bir yama var; ona ulaşmak için 802 bileşen
çağrısı, 1004 düğüm karşılaştırması ve 601 öznitelik karşılaştırması yapıldı. Karşılaştırmaların
yamaya oranı yaklaşık bin altı yüze bir.

Bu bir kusur değil, modelin tanımıdır. Çerçeve neyin değiştiğini **bilmez**; öğrenmek için
karşılaştırır. Karşılaştırmanın maliyeti değişikliğin büyüklüğüyle değil, üretilen ağacın
büyüklüğüyle orantılıdır.

Üçüncü satır bunu en açık biçimde gösteriyor: hiçbir şey değişmediğinde de aynı 802 çağrı
ve 1004 karşılaştırma yapılıyor, yalnızca sonuçta sıfır yama çıkıyor. Değişiklik olmaması
işi azaltmıyor.

Sayılar iki bileşen daha içeriyor. Bileşen çağrısı sayısı, üretilen ağacın yeniden
kurulmasıdır; her çağrı yeni nesneler ayırır ve öbekte iş üretir. Düğüm karşılaştırması ise
ağacı gezme maliyetidir. İkisi ayrı ayrı ölçülür, çünkü ayrı ayrı azaltılırlar.

#### Anahtar Uzlaşmanın Girdisidir

Dördüncü ve beşinci satırlar arasındaki fark, bu ailedeki en somut karar noktasını gösteriyor.

Başa bir satır eklendiğinde, anahtar varsa uzlaşma her satırı kimliğiyle eşleştirir. Eski
sıradaki 200 satır yeni sırada da aynı göreli düzendedir; taşınması gereken düğüm yoktur ve
tek yama yeni satırın eklenmesidir. (Örnekteki taşıma sayımı en uzun artan alt dizi
hesabına dayanır: yerinde kalabilecek en büyük düğüm kümesi bulunur, yalnızca dışarıda
kalanlar taşınır.)

Anahtar yoksa karşılaştırma **konuma** göre yapılır. Yeni listenin birinci öğesi eski
listenin birinci öğesiyle, ikincisi ikincisiyle eşleştirilir. Başa ekleme her satırı bir
konum kaydırdığı için hiçbir eşleşme tutmaz: 200 satırın adı ve değeri farklı çıkar ve 401
yama üretilir. Belgede aslında tek bir satır eklenmiş olsa bile, 401 yazma işlemi yapılır.

Sonucun ötesinde bir yan etkisi daha vardır. Konuma göre eşleşen düğümler yeniden
kullanıldığı için, o düğümlere bağlı belge durumu — odak, kaydırma konumu, girdi alanının
yazılmış içeriği — yanlış satırda kalır. Bu, listelerde anahtar kullanmanın bir başarım
tercihi değil, doğruluk gereği olmasının nedenidir.

#### Bellemenin Ödünleşimi

İkinci satır, işin nasıl azaltıldığını gösteriyor. Satır bileşeni bellendiğinde, özellikleri
değişmeyen 199 satır yeniden çalışmıyor ve önceki ağacını aynen döndürüyor. Fark alma bu
düğümlere ulaştığında başvuruların aynı olduğunu görüp alt ağaca hiç inmiyor: düğüm
karşılaştırması 1004'ten 208'e, öznitelik karşılaştırması 601'den 4'e düşüyor.

Kazanç büyük, ama koşullu. Belleme yalnızca özelliklerin **kimliği** korunduğunda çalışır.
Üst bileşen her çizimde yeni bir nesne ya da yeni bir işlev üretip özellik olarak
veriyorsa, sığ karşılaştırma her seferinde başarısız olur ve belleme hiçbir şey kazandırmaz —
üstelik karşılaştırmanın kendi maliyetini ekler. Türetilmiş Değerler dersindeki kimlik
kararlılığı sorunu, bu ailede bir başarım aracına dönüşür.

İkinci bedel görünürlüktür. Belleme elle yapılan bir işlemdir: hangi bileşenin belleneceğine
ve hangi değerlerin kimliğinin korunacağına insan karar verir. Karar yanlış verildiğinde
sonuç bir hata değil, sessiz bir yavaşlamadır.

#### Ailenin Profili

Bu ailenin özelliklerini birkaç maddede toplamak, sonraki derslerdeki karşılaştırmanın
temelini verir.

Güncelleme birimi bileşendir; ince tanelilik bileşen sınırına kadar iner, daha aşağı inmez.
Çalışma zamanı, fark alma algoritmasını ve ağaç temsilini taşımak zorundadır; bu, indirilen
kodun sabit bir bölümüdür. Bileşen işlevi saf bir dönüşüm olarak yazıldığı için üretim
işi bölünebilir: çerçeve karşılaştırmayı parçalara ayırıp aralara başka iş sokabilir, çünkü
sanal ağaç belgeye yazılmadan önce hiçbir görünür etki yaratmaz. Buna karşılık aynı saflık,
değişikliğin nereden geldiğini kaydeden bir defter tutulmadığı anlamına gelir; bilgi her
güncellemede karşılaştırmayla yeniden üretilir.

#### Özet

- Bu ailede güncelleme, durumdan yeni bir ağaç üretip önceki ağaçla karşılaştırmaktır;
  yeniden çizim birimi bileşendir.
- Karşılaştırmanın maliyeti değişikliğin büyüklüğüyle değil üretilen ağacın büyüklüğüyle
  orantılıdır: iki yüz satırlık tabloda tek hücrelik değişiklik 802 bileşen çağrısı ve 1004
  düğüm karşılaştırması üretti, belgeye tek yama yazıldı.
- Hiçbir şey değişmediğinde de aynı iş yapılır; yalnızca yama sayısı sıfır çıkar.
- Anahtar, uzlaşmanın kimlik girdisidir: anahtarlı listede başa ekleme tek yama, anahtarsız
  listede 401 yama üretti ve konuma göre eşleşen düğümlerde belge durumu yanlış satırda kalır.
- Belleme, özellikleri değişmeyen alt ağaçlara hiç inilmemesini sağlar; örnekte düğüm
  karşılaştırması 1004'ten 208'e indi.
- Belleme özelliklerin kimlik kararlılığına bağlıdır ve elle yapılır; yanlış uygulandığında
  sessizce etkisiz kalır.

#### Sonraki Adım

Bu ailedeki iş miktarının kaynağı tek bir eksikti: çerçeve neyin değiştiğini bilmiyordu,
bulmak için karşılaştırıyordu. Bilseydi karşılaştırmaya hiç gerek kalmazdı. Bilmenin yolu,
bir değerin nerede okunduğunu okuma anında kaydetmektir — o zaman değer yazıldığında,
güncellenmesi gereken yerlerin listesi zaten elde olur. Sonraki ders, bu defteri çalışma
zamanında tutan aileyi ele alır ve aynı iki yüz satırlık tabloda aynı hücre değiştiğinde
yapılan iş miktarını bu dersteki sayılarla karşılaştırır.

### Tepkisel Bağımlılık Takibi

Source: https://academia.sh/tr/kurslar/bilesen-tabanli-gelistirme/tepkisel-bagimlilik-takibi

Okuma anında kaydedilen bağımlılıkla kurulan ince taneli güncelleme; aynı tablo değişikliğinin bu ailede ürettiği iş miktarı, kayıt defterinin bedeli, elmas bağımlılıkta tutarlılık ve toplu yazma.

Önceki dersteki iş miktarının kaynağı tek bir eksikti: çerçeve neyin değiştiğini bilmiyor,
bulmak için karşılaştırıyordu. Bilseydi karşılaştırmaya gerek kalmazdı.

Bilmenin yolu, bir değerin nerede okunduğunu **okuma anında** kaydetmektir. Bir hesap
çalışırken hangi değerlere baktığı izlenir ve bu değerlerin her birine "beni okuyan şuydu"
kaydı düşülür. Değer yazıldığında, güncellenmesi gereken hesapların listesi zaten elde
olur. Bu aile, güncellemeyi bir arama problemi olmaktan çıkarıp bir **bildirim** problemine
çevirir.

#### Bağlantı, Bileşen Değil

Bu ailede güncelleme birimi bileşen değildir. Bileşen işlevi bir kez çalışır, tepkisel
değerleri ve görünümün bu değerlere bağlandığı noktaları kurar, sonra bir daha çalışmaz.
Güncellenen şey, tek bir **bağlantıdır**: belgedeki bir metin düğümü, bir öznitelik ya da
bir sınıf adı.

Üç ilkel yeter. **Sinyal (signal)**, okunabilen ve yazılabilen bir değerdir. **Türetilmiş
değer**, başka değerlerden hesaplanan ve sonucu bellenen bir değerdir. **Etki**, kaynakları
değiştiğinde yeniden çalışan bir işlevdir; görünüme yazan kod buradadır. Bileşen Yaşam
Döngüsü ve Durum konusundaki üç kavramın aynısıdır; ayrım, burada bunların bileşene değil
doğrudan birbirine bağlanmasıdır.

#### Aynı Değişiklik, Bu Kez Sayılmış

Önceki dersteki tabloyu aynı büyüklükte kuralım: iki yüz satır, tek bir hücrenin değeri
değişiyor.

```js
// sinyal.mjs — okuma anında kaydedilen bagimlilik ve ince taneli guncelleme
let sayac = { abonelik: 0, hesap: 0, etki: 0, yama: 0 };
const sifirla = () => (sayac = { abonelik: 0, hesap: 0, etki: 0, yama: 0 });

let izleyici = null;
const kuyruk = new Set();
const KIP = { sirali: true };   // false: degisiklik anlik ve derinlemesine yayilir
let topluDerinlik = 0;
const toplu = (fn) => { topluDerinlik++; try { fn(); } finally { if (--topluDerinlik === 0) bosalt(); } };

const kaydet = (kaynak) => {
  if (!izleyici) return;
  kaynak.aboneler.add(izleyici);
  izleyici.seviye = Math.max(izleyici.seviye, kaynak.seviye + 1);
  sayac.abonelik++;
};
const isaretle = (hesap) => {
  if (KIP.sirali) { if (!hesap.kirli) { hesap.kirli = true; kuyruk.add(hesap); } }
  else { hesap.kirli = true; hesap.calistir(); }
};
function bosalt() {
  while (kuyruk.size) {
    let en = null;
    for (const h of kuyruk) if (!en || h.seviye < en.seviye) en = h;   // seviye sirasi
    kuyruk.delete(en);
    if (en.kirli) en.calistir();
  }
}

function sinyal(ilk) {
  const d = { deger: ilk, aboneler: new Set(), seviye: 0 };
  return {
    oku() { kaydet(d); return d.deger; },
    yaz(v) {
      if (Object.is(v, d.deger)) return;
      d.deger = v;
      for (const a of [...d.aboneler]) isaretle(a);
      if (topluDerinlik === 0) bosalt();
    },
  };
}

function turetilmis(fn) {
  const d = { deger: undefined, aboneler: new Set(), seviye: 0, kirli: true };
  d.calistir = () => {
    const onceki = izleyici; izleyici = d;
    sayac.hesap++;
    const yeni = fn();
    izleyici = onceki;
    const degisti = !Object.is(yeni, d.deger);
    d.deger = yeni; d.kirli = false;
    if (degisti) for (const a of [...d.aboneler]) isaretle(a);
  };
  return { oku() { if (d.kirli) d.calistir(); kaydet(d); return d.deger; } };
}

function etki(fn) {
  const d = { aboneler: new Set(), seviye: 0, kirli: false };
  d.calistir = () => { const onceki = izleyici; izleyici = d; sayac.etki++; d.kirli = false; fn(); izleyici = onceki; };
  d.calistir();
  return d;
}

// --- 1. Iki yuz satirlik olcum tablosu: her hucre kendi baglantisini kurar ---
const SATIR_SAYISI = 200;
sifirla();
const belge = new Array(SATIR_SAYISI).fill("");
const sinyaller = Array.from({ length: SATIR_SAYISI }, (_, i) => sinyal(-10 + i * 0.1));
for (let i = 0; i < SATIR_SAYISI; i++) {
  etki(() => { belge[i] = `${sinyaller[i].oku().toFixed(1)} °C`; sayac.yama++; });
}
const esikAsan = turetilmis(() => sinyaller.filter((s) => s.oku() > 5).length);
let ozetMetni = "";
etki(() => { ozetMetni = `eşiği aşan: ${esikAsan.oku()}`; sayac.yama++; });
const kurulum = { ...sayac };

sifirla();
sinyaller[120].yaz(42.5);
const guncelleme = { ...sayac };

console.log("aşama       abonelik  türetilmiş hesap  etki çalışması  belge yazması");
const yaz = (ad, s) => console.log(`${ad.padEnd(11)} ${String(s.abonelik).padStart(8)} ${String(s.hesap).padStart(17)} ${String(s.etki).padStart(15)} ${String(s.yama).padStart(14)}`);
yaz("kurulum", kurulum);
yaz("güncelleme", guncelleme);
console.log("değişen hücrenin metni:", belge[120], "| özet:", ozetMetni);

// --- 2. Toplu yazma: iki yazma tek guncelleme ---
sifirla();
sinyaller[10].yaz(60);
sinyaller[11].yaz(70);
console.log("\niki ayrı yazma → türetilmiş hesap:", sayac.hesap, "| etki çalışması:", sayac.etki, "| özet:", ozetMetni);
sifirla();
toplu(() => { sinyaller[12].yaz(80); sinyaller[13].yaz(90); });
console.log("tek toplu yazma → türetilmiş hesap:", sayac.hesap, "| etki çalışması:", sayac.etki, "| özet:", ozetMetni);

// --- 3. Elmas bagimlilik: ayni kaynaktan turetilen iki deger ve onlari birlestiren ucuncu ---
const kutuk = [];
const kur = () => {
  const ham = sinyal(-4.2);
  const santigrat = turetilmis(() => `${ham.oku().toFixed(1)} °C`);
  const fahrenhayt = turetilmis(() => `${(ham.oku() * 1.8 + 32).toFixed(1)} °F`);
  const ozet = turetilmis(() => `${santigrat.oku()} / ${fahrenhayt.oku()}`);
  etki(() => kutuk.push(ozet.oku()));
  return ham;
};

console.log("\n--- seviye sirali yayilim ---");
KIP.sirali = true; kutuk.length = 0; sifirla();
kur().yaz(10);
console.log("etki çalışması:", sayac.etki, "| günlük:", JSON.stringify(kutuk));

console.log("\n--- anlik derinlemesine yayilim ---");
KIP.sirali = false; kutuk.length = 0; sifirla();
kur().yaz(10);
console.log("etki çalışması:", sayac.etki, "| günlük:", JSON.stringify(kutuk));
KIP.sirali = true;
```

```
aşama       abonelik  türetilmiş hesap  etki çalışması  belge yazması
kurulum          401                 1             201            201
güncelleme       202                 1               2              2
değişen hücrenin metni: 42.5 °C | özet: eşiği aşan: 50

iki ayrı yazma → türetilmiş hesap: 2 | etki çalışması: 4 | özet: eşiği aşan: 52
tek toplu yazma → türetilmiş hesap: 1 | etki çalışması: 3 | özet: eşiği aşan: 54

--- seviye sirali yayilim ---
etki çalışması: 2 | günlük: ["-4.2 °C / 24.4 °F","10.0 °C / 50.0 °F"]

--- anlik derinlemesine yayilim ---
etki çalışması: 3 | günlük: ["-4.2 °C / 24.4 °F","10.0 °C / 24.4 °F","10.0 °C / 50.0 °F"]
```

#### İki Ailenin Sayıları

Güncelleme satırı önceki dersle yan yana konduğunda ayrım keskindir. Sanal ağaç ailesinde
tek hücrelik değişiklik 802 bileşen çağrısı, 1004 düğüm karşılaştırması ve 601 öznitelik
karşılaştırması üretmişti. Burada bir türetilmiş hesap, iki etki çalışması ve iki belge
yazması var. Ağaç hiç gezilmiyor, hiçbir karşılaştırma yapılmıyor.

İki belge yazmasının biri değişen hücre, diğeri eşik özeti. Özet, iki yüz sinyalin
tamamına bağlı bir türetilmiş değerdir; bu yüzden hangi hücre değişirse değişsin yeniden
hesaplanır. **Toplayan hesaplar ince taneliliğin dışında kalır**: bir değer her şeye
bağlıysa, her değişiklikte çalışır. Ailenin kazancı bağlantıların yerelliğinden gelir, veri
akışının yapısını değiştirmez.

Sayının küçüklüğü ölçek büyüdükçe daha da belirginleşir. Sanal ağaç ailesinde iş miktarı
satır sayısıyla doğru orantılıdır; burada değişen bağlantı sayısıyla orantılıdır. İki bin
satırlık bir tabloda ilk sayı on katına çıkar, ikincisi değişmez.

#### Defterin Bedeli

Kurulum satırı bedeli gösteriyor: 401 abonelik, 201 etki çalışması. Her hücre için bir etki,
her etki için bir abonelik; artı özet hesabının iki yüz sinyale birden abone olması.

Bu, bellekte tutulan bir çizgedir. Her sinyal abone kümesi taşır, her hesap kaynak listesi
taşır. Sanal ağaç ailesinde böyle bir yapı yoktur: orada tutulan tek şey bir önceki ağaçtır
ve ilişkiler her karşılaştırmada yeniden türetilir. Burada ilişkiler kalıcıdır ve bellekte
yer kaplar.

Güncelleme satırındaki 202 abonelik ikinci bedeli gösteriyor. Bir hesap her çalıştığında
bağımlılıklarını **yeniden** kaydeder; koşullu bir okuma bir sonraki çalışmada başka bir
kaynağa bağlanabileceği için kayıt her seferinde tazelenmek zorundadır. Özet hesabı iki yüz
sinyali yeniden okuyup yeniden abone oluyor. Defterin bakımı da bir iş kalemidir.

Üçüncü bedel görünmez bağlantıdır. Bir değerin nerede okunduğu kaynak metinde
işaretlenmemiştir; okumanın kendisi kayıt yaptırır. Bir okumayı yanlış yere koymak — örneğin
bir etkinin dışına çıkarmak — bağlantıyı sessizce koparır ve görünüm güncellenmez. Konum
tabanlı kancalardaki çağrı sırası kuralı gibi, bu da dilin denetlemediği bir sözleşmedir.

#### Elmas Bağımlılık ve Tutarlılık

Son iki bölüm, bildirim tabanlı bir sistemin en ince sorununu gösteriyor.

Ham ölçümden iki değer türetiliyor — santigrat ve fahrenhayt — ve üçüncü bir değer ikisini
birleştiriyor. Ham değer yazıldığında iki türetilmiş değer de geçersizleşir, ama sırayla
güncellenirler. Yayılım anlık ve derinlemesine yapılırsa, santigrat güncellendiği anda özet
de güncellenir; oysa fahrenhayt hâlâ eski değerindedir.

Günlükteki ikinci satır bunu gösteriyor: `10.0 °C / 24.4 °F`. Bu değer hiçbir zaman
gerçek olmadı — santigrat yeni, fahrenhayt eski. Buna **tutarsız ara değer (glitch)** denir
ve etkiyi bir kez fazladan çalıştırmakla kalmaz, o çalıştırmada yanlış bir görüntü üretir.

Seviye sıralı yayılımda bu olmuyor. Her hesaba, kaynaklarının en büyük seviyesinden bir
fazla seviye verilir; güncelleme kuyruğu seviye sırasıyla boşaltılır. Böylece bir hesap,
bütün kaynakları güncellendikten sonra çalışır. Günlükte yalnızca başlangıç değeri ve
tutarlı son değer var.

Bu, ailenin gerçekleştirim ayrıntısı gibi görünen ama doğrudan doğruluk taşıyan bir
özelliğidir. Bir çerçevenin tepkisel çekirdeği değerlendirilirken sorulacak sorulardan biri,
tutarsız ara değerlerin tanım gereği engellenip engellenmediğidir.

#### Toplu Yazma

İkinci bölüm ikinci bir tutarlılık aracını gösteriyor. İki sinyale ayrı ayrı yazıldığında
özet hesabı iki kez çalışıyor ve etkiler dört kez tetikleniyor. Aynı iki yazma tek bir toplu
işlemde yapıldığında özet bir kez hesaplanıyor, etki sayısı üçe iniyor.

Kazanç yalnızca iş miktarı değildir. Toplu yazma, iki değişikliğin **tek bir geçiş** olarak
görünmesini sağlar; arada oluşan ve hiçbir zaman geçerli olmayan durum dışarıya sızmaz.
Bileşen Yaşam Döngüsü ve Durum konusundaki toplu işleme kavramının aynısıdır; burada
bileşen sınırına değil, açıkça işaretlenen bir bloğa bağlıdır.

#### Ailenin Profili

Güncelleme birimi bağlantıdır; ince tanelilik tek bir metin düğümüne kadar iner. İş miktarı
üretilen ağacın büyüklüğüyle değil değişen bağlantı sayısıyla orantılıdır ve belleme çoğu
durumda gerekmez — türetilmiş değer zaten bellenmiştir.

Karşılığında çalışma zamanı, bağımlılık çizgesini kurmak ve bakımını yapmak zorundadır:
kurulum daha pahalıdır, bellek kullanımı bağlantı sayısıyla artar ve her hesap çalıştığında
kayıtlar tazelenir. Bileşen işlevinin bir kez çalışması, "her çizimde baştan" alışkanlığına
göre farklı bir zihinsel model ister; kurulumda okunan bir değer donar, izlenmesi için
tepkisel olarak okunması gerekir.

#### Özet

- Bu ailede bağımlılık okuma anında kaydedilir; güncelleme birimi bileşen değil tek bir
  bağlantıdır.
- Aynı tek hücrelik değişiklik, sanal ağaç ailesinde 802 bileşen çağrısı ve 1004 düğüm
  karşılaştırması üretirken burada bir türetilmiş hesap ve iki belge yazması üretti.
- Toplayan hesaplar ince tanelilik dışında kalır: bütün sinyallere bağlı bir türetilmiş
  değer, hangi sinyal değişirse değişsin yeniden hesaplanır.
- Kayıt defteri bellekte tutulur ve bakım ister; her hesap çalıştığında bağımlılıklarını
  yeniden kaydeder.
- Anlık ve derinlemesine yayılım elmas bağımlılıkta tutarsız ara değer üretir; seviye sıralı
  yayılım bunu tanım gereği engeller.
- Toplu yazma hem iş miktarını azaltır hem de ara durumun dışarı sızmasını önler.

#### Sonraki Adım

İki aile de defterini çalışma zamanında tutuyor: biri ağacı karşılaştırarak, diğeri
abonelikleri kaydederek. İkisinin de indirilen kodda bir karşılığı var — fark alma algoritması
ya da bağımlılık çizgesi düzeneği, sayfa açıldığı anda tarayıcıya inmek zorunda. Oysa hangi
ifadenin hangi değere bağlı olduğu, çoğu durumda kaynak metne bakarak da anlaşılabilir. Bir
şablonda bir metin düğümünün hangi değişkeni okuduğu yazılıdır. Sonraki ders, bu çıkarımı
kod çalışmadan önce yapan yaklaşımı ele alır: bağımlılıkları derleme zamanında çözen ve
çalışma zamanına yalnızca güncelleme kodunu bırakan bir dönüştürücü yazar.

### Derleyici Tabanlı Yaklaşımlar

Source: https://academia.sh/tr/kurslar/bilesen-tabanli-gelistirme/derleyici-tabanli-yaklasimlar

Bağımlılıkların derleme zamanında çözülmesi; şablonu güncelleme koduna çeviren küçük bir dönüştürücü, üretilen kodun okunması, çözümlemenin sağlamlık sınırı ve derleme adımının bedeli.

Önceki iki aile de defterini çalışma zamanında tutuyordu: biri ağacı karşılaştırarak,
diğeri abonelikleri kaydederek. İkisinin de indirilen kodda bir karşılığı vardı.

Oysa hangi ifadenin hangi değere bağlı olduğu, çoğu durumda kaynak metne bakarak
anlaşılabilir. Bir şablonda bir metin düğümünün hangi değişkeni okuduğu yazılıdır. Bunu
kod çalışmadan önce çıkarabilen bir araç, çalışma zamanına yalnızca "şu değer değişirse şu
yeri yaz" biçiminde doğrudan kod bırakabilir.

#### Bağımlılık Kaynak Metinde Yazılıdır

Bilgisayarlar Nasıl Çalışır kursundaki Derleme Aşamaları dersinde kurulan zincir burada
küçük ölçekte tekrarlanır: sözcüksel çözümleme, ağaç kurma, çözümleme ve kod üretimi. Girdi
bir şablon, çıktı çalıştırılabilir güncelleme kodudur.

Ayrım şuradadır: önceki iki ailede çerçeve, uygulamanın **verisini** yorumlar; bu ailede
araç, uygulamanın **kaynak metnini** yorumlar. Yorumlama bir kez, derleme sırasında yapılır
ve sonucu bir daha değişmez.

Aşağıdaki dönüştürücü bunu en yalın biçimde kurar. Şablonu durağan parçalara ve ifade
yuvalarına böler, her ifadenin okuduğu durum alanlarını bulur ve alan başına yalnızca o
alandan etkilenen yuvaları yazan bir işlev üretir.

```js
// derleyici.mjs — sablonu derleme zamaninda cozup guncelleme kodu ureten donusturucu
const DURUM_ALANLARI = ["ad", "deger", "damga", "esikAsildi"];

const SABLON = `<tr class="satir {esikAsildi ? 'uyari' : 'olagan'}">
  <th scope="row">{ad}</th>
  <td class="deger">{deger.toFixed(1)} °C</td>
  <td class="damga">{esikAsildi ? damga : '—'}</td>
</tr>`;

// 1) Ayristirma: sablon, duragan parcalara ve ifade yuvalarina bolunur.
function ayristir(sablon) {
  const duragan = [];
  const ifadeler = [];
  let son = 0;
  for (const eslesme of sablon.matchAll(/\{([^{}]*)\}/g)) {
    duragan.push(sablon.slice(son, eslesme.index));
    ifadeler.push(eslesme[1].trim());
    son = eslesme.index + eslesme[0].length;
  }
  duragan.push(sablon.slice(son));
  return { duragan, ifadeler };
}

// Dizgi degismezlerini ayirmadan hicbir cozumleme dogru olamaz.
const dizgisiz = (ifade) => ifade.replace(/'[^']*'|"[^"]*"/g, (d) => " ".repeat(d.length));

// 2) Cozumleme: her ifadenin okudugu durum alanlari bulunur.
function cozumle(ifade) {
  const g = dizgisiz(ifade);
  const dinamik = /\[[^\]]*\]/.test(g);                    // durum[degisken] gibi erisim
  const adlar = DURUM_ALANLARI.filter((a) => new RegExp(`(?<![.\\w$])${a}\\b`).test(g));
  return { adlar, dinamik };
}

// 3) Kod uretimi: ifadedeki durum adlari `d.` ile nitelenir.
function nitele(ifade) {
  const g = dizgisiz(ifade);
  let sonuc = "";
  let son = 0;
  const eslesmeler = [];
  for (const a of DURUM_ALANLARI)
    for (const e of g.matchAll(new RegExp(`(?<![.\\w$])${a}\\b`, "g"))) eslesmeler.push({ konum: e.index, uzunluk: a.length });
  eslesmeler.sort((x, y) => x.konum - y.konum);
  for (const e of eslesmeler) {
    sonuc += ifade.slice(son, e.konum) + "d." + ifade.slice(e.konum, e.konum + e.uzunluk);
    son = e.konum + e.uzunluk;
  }
  return sonuc + ifade.slice(son);
}

function derle(sablon) {
  const { duragan, ifadeler } = ayristir(sablon);
  const cozum = ifadeler.map(cozumle);
  const govdeler = ifadeler.map((ifade, i) => `y[${i}] = String(${nitele(ifade)});`);
  const etkilenen = {};
  for (const alan of DURUM_ALANLARI)
    etkilenen[alan] = cozum.map((c, i) => (c.dinamik || c.adlar.includes(alan) ? i : -1)).filter((i) => i >= 0);

  const kaynak = [
    `const DURAGAN = ${JSON.stringify(duragan)};`,
    `const yuvaYaz = [`,
    ...govdeler.map((g, i) => `  (d, y) => { ${g} },   // yuva ${i}`),
    `];`,
    `const kur = (d) => { const y = []; for (const f of yuvaYaz) f(d, y); return y; };`,
    ...DURUM_ALANLARI.map((a) =>
      `const ${a}Degisti = (d, y) => { ${etkilenen[a].map((i) => `yuvaYaz[${i}](d, y);`).join(" ")} return ${etkilenen[a].length}; };`),
    `const metin = (y) => DURAGAN.reduce((m, s, i) => m + s + (y[i] ?? ""), "");`,
  ].join("\n");

  // Uretilen kodu calistirilabilir hale getir.
  const yuvaYaz = govdeler.map((g) => new Function("d", "y", g));
  const modul = {
    kur: (d) => { const y = []; for (const f of yuvaYaz) f(d, y); return y; },
    metin: (y) => duragan.reduce((m, s, i) => m + s + (y[i] ?? ""), ""),
  };
  for (const a of DURUM_ALANLARI)
    modul[`${a}Degisti`] = (d, y) => { for (const i of etkilenen[a]) yuvaYaz[i](d, y); return etkilenen[a].length; };
  return { kaynak, duragan, ifadeler, cozum, etkilenen, modul };
}

const derlenmis = derle(SABLON);
console.log("--- üretilen kod ---");
console.log(derlenmis.kaynak);

console.log("\n--- ifade / bağımlılık tablosu ---");
derlenmis.ifadeler.forEach((ifade, i) =>
  console.log(`  yuva ${i}: ${ifade.padEnd(32)} → ${JSON.stringify(derlenmis.cozum[i].adlar)}`));
console.log("  alan → etkilenen yuvalar:", JSON.stringify(derlenmis.etkilenen));

const m = derlenmis.modul;
const durum = { ad: "sensör-120", deger: 42.5, damga: "06:00", esikAsildi: true };
const yuvalar = m.kur(durum);
console.log("\nilk çizim:");
console.log(m.metin(yuvalar));

durum.deger = 43.1;
console.log(`\ndeğer değişti → yeniden değerlendirilen ifade: ${m.degerDegisti(durum, yuvalar)}`);
console.log(m.metin(yuvalar));

durum.esikAsildi = false;
console.log(`\neşik durumu değişti → yeniden değerlendirilen ifade: ${m.esikAsildiDegisti(durum, yuvalar)}`);
console.log(m.metin(yuvalar));

console.log("\ndeğişen alan     derlenmiş  çözümleme yapmayan çalışma zamanı");
for (const alan of DURUM_ALANLARI)
  console.log(`${alan.padEnd(16)} ${String(derlenmis.etkilenen[alan].length).padStart(9)} ${String(derlenmis.ifadeler.length).padStart(34)}`);

// 4) Cozumlemenin siniri: dinamik erisim butun alanlara bagimlilik uretir.
const belirsiz = derle(`<td>{deger[ad]}</td>`);
console.log("\ndinamik erişimli şablon:");
console.log("  ifade:", belirsiz.ifadeler[0], "| dinamik:", belirsiz.cozum[0].dinamik);
console.log("  alan → etkilenen yuvalar:", JSON.stringify(belirsiz.etkilenen));
```

```
--- üretilen kod ---
const DURAGAN = ["<tr class=\"satir ","\">\n  <th scope=\"row\">","</th>\n  <td class=\"deger\">"," °C</td>\n  <td class=\"damga\">","</td>\n</tr>"];
const yuvaYaz = [
  (d, y) => { y[0] = String(d.esikAsildi ? 'uyari' : 'olagan'); },   // yuva 0
  (d, y) => { y[1] = String(d.ad); },   // yuva 1
  (d, y) => { y[2] = String(d.deger.toFixed(1)); },   // yuva 2
  (d, y) => { y[3] = String(d.esikAsildi ? d.damga : '—'); },   // yuva 3
];
const kur = (d) => { const y = []; for (const f of yuvaYaz) f(d, y); return y; };
const adDegisti = (d, y) => { yuvaYaz[1](d, y); return 1; };
const degerDegisti = (d, y) => { yuvaYaz[2](d, y); return 1; };
const damgaDegisti = (d, y) => { yuvaYaz[3](d, y); return 1; };
const esikAsildiDegisti = (d, y) => { yuvaYaz[0](d, y); yuvaYaz[3](d, y); return 2; };
const metin = (y) => DURAGAN.reduce((m, s, i) => m + s + (y[i] ?? ""), "");

--- ifade / bağımlılık tablosu ---
  yuva 0: esikAsildi ? 'uyari' : 'olagan'  → ["esikAsildi"]
  yuva 1: ad                               → ["ad"]
  yuva 2: deger.toFixed(1)                 → ["deger"]
  yuva 3: esikAsildi ? damga : '—'         → ["damga","esikAsildi"]
  alan → etkilenen yuvalar: {"ad":[1],"deger":[2],"damga":[3],"esikAsildi":[0,3]}

ilk çizim:
<tr class="satir uyari">
  <th scope="row">sensör-120</th>
  <td class="deger">42.5 °C</td>
  <td class="damga">06:00</td>
</tr>

değer değişti → yeniden değerlendirilen ifade: 1
<tr class="satir uyari">
  <th scope="row">sensör-120</th>
  <td class="deger">43.1 °C</td>
  <td class="damga">06:00</td>
</tr>

eşik durumu değişti → yeniden değerlendirilen ifade: 2
<tr class="satir olagan">
  <th scope="row">sensör-120</th>
  <td class="deger">43.1 °C</td>
  <td class="damga">—</td>
</tr>

değişen alan     derlenmiş  çözümleme yapmayan çalışma zamanı
ad                       1                                  4
deger                    1                                  4
damga                    1                                  4
esikAsildi               2                                  4

dinamik erişimli şablon:
  ifade: deger[ad] | dinamik: true
  alan → etkilenen yuvalar: {"ad":[0],"deger":[0],"damga":[0],"esikAsildi":[0]}
```

#### Üretilen Kodun Okunması

Çıktının ilk bölümü, çalışma zamanına inen şeyin ne olduğunu gösteriyor: dört yuva yazma
işlevi, alan başına bir güncelleme işlevi ve bir metin birleştirici. Fark alma algoritması
yok, abone kümesi yok, ağaç temsili yok. Kod, hangi yerin hangi değişiklikte yazılacağını
**doğrudan** söylüyor.

`esikAsildiDegisti` iki yuvayı birden yazıyor, çünkü eşik durumu hem satırın sınıf adında
hem de damga hücresinde okunuyor. Bu bilgi bağımlılık tablosundan geliyor ve çalışma
zamanında hiçbir hesaba dayanmıyor.

`damgaDegisti` yalnızca dördüncü yuvayı yazıyor. Damga değiştiğinde satırın sınıf adı yeniden
hesaplanmıyor — çünkü sınıf adı damgayı okumuyor. Çözümleme yapmayan bir çalışma zamanı
hangi alan değişirse değişsin dört ifadeyi de yeniden değerlendirirdi; tablo bu farkı
gösteriyor.

Durağan parçaların da ayrılmış olması ikinci bir kazançtır. `<th scope="row">` metni bir
kez üretilir ve bir daha hiçbir güncellemede dokunulmaz. Sanal ağaç ailesinde bu düğüm her
karşılaştırmada gezilir; burada karşılaştırma diye bir adım yoktur.

#### Çözümlemenin Sağlamlık Sınırı

Son bölüm yaklaşımın kırılma noktasını gösteriyor. `deger[ad]` ifadesinde okunan alan
kaynak metinden belli değildir; hangi anahtarın kullanılacağı çalışma zamanında belli olur.
Çözümleyici bu durumda **muhafazakâr** davranmak zorundadır: ifadeyi bütün alanlara bağımlı
sayar, böylece hangi alan değişirse değişsin yuva yeniden yazılır.

Bu, derleyici tabanlı yaklaşımın genel kuralıdır. Çıkarım **sağlam** olmalıdır: bir
bağımlılığı kaçırmak, güncellenmeyen bir görünüm demektir ve bu bir başarım sorunu değil,
doğruluk sorunudur. Fazladan bağımlılık eklemek yalnızca gereksiz iş üretir. Belirsizlik
karşısında araç her zaman fazlayı seçer.

Sonuç, dilin bir alt kümesine yönelmedir. Şablon dilleri, ifade sözdizimini bilerek kısıtlar:
sınırlı ifade biçimleri, sınırlı erişim yolları, açıkça bildirilen durum. Kısıt bir eksiklik
değil, çıkarımın çalışabilmesinin koşuludur. Aynı fikir Düzen Sistemleri kursundaki ön
işlemcilerde de vardır — girdi dili ne kadar dar olursa, araç o kadar çok şey söyleyebilir.

Çözümleme aynı zamanda **kullanılmayan** yolları da görür. Bir durum alanı hiçbir ifadede
okunmuyorsa, o alan için üretilen güncelleme işlevi boş kalır; kullanılmayan bileşenler ve
dallar paketten tümüyle çıkarılabilir. Derleme Aşamaları dersindeki ölü kod eleme, burada
uygulama düzeyine taşınır.

#### Derleme Adımının Bedeli

Kazançların karşılığı, çalıştırılan kodun yazılan kod olmamasıdır.

Hata ayıklama bunun ilk sonucudur. Kesme noktası konan satır ile çalışan satır ayrıdır;
aradaki bağı kaynak haritası kurar. Tarayıcı ve Web Platformu kursundaki Kaynak Hata Ayıklama
dersinde tanıtılan kaynak haritası bu ailede isteğe bağlı bir kolaylık değil, zorunlu bir
parçadır. Eşleme eksik ya da bozuksa, hata iletisi anlaşılmaz bir konumu gösterir.

İkinci bedel araç zinciri bağımlılığıdır. Kod tarayıcıda doğrudan çalışmaz; bir derleme
adımından geçmek zorundadır. Geliştirme sırasında bu adımın hızlı olması gerekir, yoksa her
değişiklikte beklenir. Sürüm yükseltmelerinde de derleyici ile yazılan kaynak metin arasında
uyum gözetilir: aracın sürümü, uygulamanın kaynak metninin bir parçası hâline gelir.

Üçüncü bedel öngörülebilirliktir. Üretilen kod okunabilir olsa bile, yazılan kodun ne
üreteceği her zaman apaçık değildir. Bir ifadeyi bir yardımcı işleve taşımak, çözümleyicinin
bağımlılığı görmesini engelleyip muhafazakâr moda düşürebilir; sonuç, hiçbir hata vermeden
artan bir iş miktarıdır. Bu ailede başarım sorunlarının tanısı, üretilen kodun okunmasını
gerektirir.

#### Ailenin Profili

Güncelleme birimi, derleme zamanında belirlenmiş tek bir yazma işlemidir. Çalışma zamanı
kodu, uygulamanın karmaşıklığıyla değil, kullanılan özelliklerle orantılıdır; genel amaçlı
bir güncelleme düzeneği taşınmaz.

Karşılığında bir derleme adımı, bir kaynak haritası zinciri ve girdi dili üzerinde bir
kısıt gelir. Çözümleme sağlam kalmak zorunda olduğu için dinamik yapılar muhafazakâr
bağımlılık üretir; dilin ifade gücü ile çıkarımın keskinliği arasında doğrudan bir ödünleşim
vardır.

Bu aile ile önceki iki aile arasındaki sınır kesin değildir. Bağımlılığı çalışma zamanında
izleyen çerçeveler de şablonlarını derler; sanal ağaç tutan çerçeveler de derleme zamanında
değişmez alt ağaçları işaretleyip karşılaştırmadan çıkarabilir. Ayrım, kararın **nerede**
verildiğinin ağırlığıdır.

#### Özet

- Bu ailede hangi ifadenin hangi değere bağlı olduğu, kod çalışmadan önce kaynak metinden
  çıkarılır ve sonuç doğrudan güncelleme kodu olarak üretilir.
- Üretilen kodda fark alma algoritması, abone kümesi ve ağaç temsili yoktur; alan başına
  hangi yuvaların yazılacağı sabittir.
- Durağan parçalar bir kez üretilir ve hiçbir güncellemede gezilmez.
- Çıkarım sağlam olmak zorundadır: dinamik erişim gibi belirsiz durumlarda araç muhafazakâr
  davranıp bütün alanlara bağımlılık üretir.
- Kısıtlı bir girdi dili, keskin çıkarımın koşuludur; kullanılmayan yolların paketten
  çıkarılması da aynı çözümlemeden gelir.
- Bedel, çalıştırılan kodun yazılan kod olmamasıdır: kaynak haritası zorunlu bir parçaya,
  aracın sürümü kaynak metnin bir bağımlılığına dönüşür.

#### Sonraki Adım

Üç aile de aynı soruya yanıt veriyordu: durum değiştiğinde belgenin neresi yazılacak. Bir
uygulamanın bileşen katmanının üstünde ise başka sorular var — adresin hangi görünüme
karşılık geldiği, formun doğrulama kurallarını nereden aldığı, bir servisin bileşene nasıl
ulaştığı. Şimdiye kadar ele alınan ailelerde bu soruların yanıtı çerçevenin dışındadır ve
her ekip kendi birleşimini kurar. Bir başka yaklaşım, bu yanıtları çerçevenin kendisiyle
birlikte vermektir. Sonraki ders, yönlendirmeyi, form modelini ve bağımlılık çözümünü tek
bir sözleşmede toplayan çerçeveleri ele alır ve bütünleşikliğin karar yüzeyini nasıl
küçülttüğünü, buna karşılık neyi bağladığını sayarak gösterir.

### Tam Kapsamlı Çerçeveler

Source: https://academia.sh/tr/kurslar/bilesen-tabanli-gelistirme/tam-kapsamli-cerceveler

Yönlendirmeyi, form modelini ve bağımlılık çözümünü tek sözleşmede toplayan çerçeveler; kapsamlı bağımlılık çözümünün çalışması, tekil örnek tuzağı, karar yüzeyinin sayılması ve sözleşme birliğinin bedeli.

Önceki üç aile de aynı soruya yanıt veriyordu: durum değiştiğinde belgenin neresi
yazılacak. Bir uygulamanın bileşen katmanının üstünde ise başka sorular var. Kuzey Yamaç
Ölçüm İstasyonu sayfası büyüdüğünde adresin hangi görünüme karşılık geldiği, süzgeç
panelinin doğrulama kurallarını nereden aldığı ve ölçüm servisinin bileşene nasıl ulaştığı
kararlaştırılmak zorundadır.

İlk üç ailede bu kararlar çerçevenin dışındadır; her ekip kendi birleşimini kurar. Dördüncü
yaklaşım, yanıtları çerçevenin kendisiyle birlikte vermektir.

#### Çatının Kapsadığı Roller

**Tam kapsamlı çerçeve**, görünüm katmanının yanında yönlendirmeyi, form modelini, veri
getirmeyi, bağımlılık çözümünü, sunucuda oluşturmayı ve sınama araçlarını da tek bir
sözleşme altında sunar. Parçalar ayrı ayrı seçilmez; birlikte gelir, birlikte sürümlenir ve
birbirini tanıyacak biçimde tasarlanır.

Bu ailenin en ayırt edici parçası **bağımlılık enjeksiyonudur (dependency injection)**.
Bileşen, ihtiyaç duyduğu servisi kendisi kurmaz; bir anahtarla ister ve çerçeve o anahtara
kayıtlı sağlayıcıyı bulup örneği verir. Programlama Temelleri kursundaki arayüz–gerçekleştirim
ayrımı burada çalışma zamanına taşınmış olur.

#### Kapsamlı Bağımlılık Çözümü

Düzenek üç parçadan oluşur: sağlayıcı kaydı, kapsam zinciri ve örneklerin nerede yaşadığını
belirleyen kural.

```js
// enjeksiyon.mjs — kapsamli bagimlilik cozumu: kayit, kapsam zinciri ve degistirme
const kapsamAc = (ad, ust = null) => ({ ad, ust, saglayicilar: new Map(), ornekler: new Map() });
const kaydet = (kapsam, anahtar, uret, { kapsamBasina = false } = {}) =>
  kapsam.saglayicilar.set(anahtar, { uret, kapsamBasina });

const iz = [];
function coz(kapsam, anahtar, zincir = []) {
  if (zincir.includes(anahtar)) throw new Error(`döngüsel bağımlılık: ${[...zincir, anahtar].join(" → ")}`);
  let sahip = kapsam;
  while (sahip && !sahip.saglayicilar.has(anahtar)) sahip = sahip.ust;
  if (!sahip) throw new Error(`kayıtlı sağlayıcı yok: ${anahtar} (istek: ${kapsam.ad})`);
  const kayit = sahip.saglayicilar.get(anahtar);
  const barinak = kayit.kapsamBasina ? kapsam : sahip;      // ornek nerede yasayacak
  if (barinak.ornekler.has(anahtar)) {
    iz.push(`${kapsam.ad}: ${anahtar} ← ${barinak.ad} (var olan örnek)`);
    return barinak.ornekler.get(anahtar);
  }
  iz.push(`${kapsam.ad}: ${anahtar} ← ${sahip.ad} (yeni örnek, ${kayit.kapsamBasina ? "kapsam başına" : "tekil"})`);
  const ornek = kayit.uret((a) => coz(kapsam, a, [...zincir, anahtar]));
  barinak.ornekler.set(anahtar, ornek);
  return ornek;
}

// --- Uygulama kaydi ---
const kok = kapsamAc("kök");
let sayac = 0;
kaydet(kok, "OlcumKaynagi", () => ({ kimlik: ++sayac, getir: () => [{ ad: "Üst yamaç", deger: -4.2 }] }));
kaydet(kok, "BirimBicimleyici", () => ({ bicimle: (c) => `${c.toFixed(1)} °C` }));
kaydet(kok, "OlcumServisi", (c) => {
  const kaynak = c("OlcumKaynagi"), bicimleyici = c("BirimBicimleyici");
  return { kimlik: ++sayac, satirlar: () => kaynak.getir().map((o) => `${o.ad}: ${bicimleyici.bicimle(o.deger)}`) };
});
kaydet(kok, "SuzgecDurumu", () => ({ kimlik: ++sayac, esik: -5 }), { kapsamBasina: true });

const tabloKapsami = kapsamAc("ölçüm tablosu", kok);
const panelKapsami = kapsamAc("süzgeç paneli", kok);

console.log("--- cozum izi ---");
const t = coz(tabloKapsami, "OlcumServisi");
const p = coz(panelKapsami, "OlcumServisi");
const ts = coz(tabloKapsami, "SuzgecDurumu");
const ps = coz(panelKapsami, "SuzgecDurumu");
for (const s of iz) console.log(" ", s);
console.log("\nölçüm servisi aynı örnek mi:", t === p);
console.log("süzgeç durumu aynı örnek mi:", ts === ps, `(kimlikler: ${ts.kimlik}, ${ps.kimlik})`);
console.log("tablonun satırları:", JSON.stringify(t.satirlar()));

// --- Sinamada degistirme: (a) var olan kokun altinda, (b) taze kokle ---
const altKapsam = kapsamAc("sınama (kök altında)", kok);
kaydet(altKapsam, "OlcumKaynagi", () => ({ kimlik: 0, getir: () => [{ ad: "Sahte", deger: 0 }] }));
iz.length = 0;
const altServis = coz(altKapsam, "OlcumServisi");
console.log("\n--- (a) var olan kokun altinda degistirme ---");
for (const s of iz) console.log(" ", s);
console.log("satırlar:", JSON.stringify(altServis.satirlar()), "| kök örneğiyle aynı mı:", altServis === t);

const tazeKok = kapsamAc("taze kök");
for (const [anahtar, kayit] of kok.saglayicilar) tazeKok.saglayicilar.set(anahtar, kayit);
kaydet(tazeKok, "OlcumKaynagi", () => ({ kimlik: 0, getir: () => [{ ad: "Sahte", deger: 0 }] }));
iz.length = 0;
const tazeServis = coz(kapsamAc("sınama (taze kök)", tazeKok), "OlcumServisi");
console.log("\n--- (b) taze kokle degistirme ---");
for (const s of iz) console.log(" ", s);
console.log("satırlar:", JSON.stringify(tazeServis.satirlar()), "| kök örneğiyle aynı mı:", tazeServis === t);

// --- Hatalar acik bildirilir ---
console.log("\n--- hatalar ---");
kaydet(kok, "A", (c) => ({ b: c("B") }));
kaydet(kok, "B", (c) => ({ a: c("A") }));
for (const deneme of [() => coz(tabloKapsami, "Kutuk"), () => coz(kok, "A")]) {
  try { deneme(); } catch (h) { console.log(" ", h.message); }
}
```

```
--- cozum izi ---
  ölçüm tablosu: OlcumServisi ← kök (yeni örnek, tekil)
  ölçüm tablosu: OlcumKaynagi ← kök (yeni örnek, tekil)
  ölçüm tablosu: BirimBicimleyici ← kök (yeni örnek, tekil)
  süzgeç paneli: OlcumServisi ← kök (var olan örnek)
  ölçüm tablosu: SuzgecDurumu ← kök (yeni örnek, kapsam başına)
  süzgeç paneli: SuzgecDurumu ← kök (yeni örnek, kapsam başına)

ölçüm servisi aynı örnek mi: true
süzgeç durumu aynı örnek mi: false (kimlikler: 3, 4)
tablonun satırları: ["Üst yamaç: -4.2 °C"]

--- (a) var olan kokun altinda degistirme ---
  sınama (kök altında): OlcumServisi ← kök (var olan örnek)
satırlar: ["Üst yamaç: -4.2 °C"] | kök örneğiyle aynı mı: true

--- (b) taze kokle degistirme ---
  sınama (taze kök): OlcumServisi ← taze kök (yeni örnek, tekil)
  sınama (taze kök): OlcumKaynagi ← taze kök (yeni örnek, tekil)
  sınama (taze kök): BirimBicimleyici ← taze kök (yeni örnek, tekil)
satırlar: ["Sahte: 0.0 °C"] | kök örneğiyle aynı mı: false

--- hatalar ---
  kayıtlı sağlayıcı yok: Kutuk (istek: ölçüm tablosu)
  döngüsel bağımlılık: A → B → A
```

İzin ilk üç satırı çözümün nasıl indiğini gösteriyor. Ölçüm servisi istendiğinde, sağlayıcısı
kök kapsamda bulunuyor; servis kurulurken kendi bağımlılıkları aynı yolla çözülüyor. Bileşen
hiçbirinin nasıl kurulduğunu bilmiyor; yalnızca anahtarları biliyor.

Dördüncü satır tekil davranışı gösteriyor: süzgeç paneli aynı servisi istediğinde yeni örnek
üretilmiyor, kök kapsamdaki örnek dönüyor. Beşinci ve altıncı satırlarda ise sağlayıcı
kapsam başına kayıtlı olduğu için iki kapsam iki ayrı örnek alıyor. Kayıt kökte, örnek
isteyen kapsamda yaşıyor.

Bu ayrım tasarım kararıdır ve doğrudan doğruluğu etkiler. Ölçüm kaynağı paylaşılmalıdır —
iki bileşenin ayrı bağlantı açması gereksizdir. Süzgeç durumu paylaşılmamalıdır — tablonun
süzgeci panelin süzgeciyle aynı olsaydı, iki bileşen birbirinin durumunu ezerdi.

#### Tekil Örnek Tuzağı

(a) ve (b) bölümleri, bütün kapsamlı çözüm düzeneklerinin ortak tuzağını gösteriyor.

Sınama kapsamında ölçüm kaynağı sahtesiyle değiştirilmiş, ama sonuç değişmemiş. Nedeni
izde yazılı: ölçüm servisi zaten kök kapsamda kurulmuş ve örneği orada duruyor. Yeni kapsam
servisi istediğinde var olan örnek dönüyor; o örnek gerçek kaynağı kurulum sırasında almış
ve bir daha sormuyor.

Doğru yol (b) bölümündedir: sınama için taze bir kök kurulur, sağlayıcı kayıtları oraya
kopyalanır ve değiştirilecek anahtar taze kökte yeniden kaydedilir. Hiçbir örnek henüz
kurulmadığı için çözüm baştan yapılır ve sahte kaynak zincire girer.

Kural şudur: **değiştirme, örnek kurulmadan önce yapılmalıdır.** Tekil kapsamdaki bir
bağımlılığın değiştirilmesi, o kapsamın tümüyle yeniden kurulmasını gerektirir.

Hata iletileri de bu ailenin bir özelliğidir. Kayıtsız bir anahtar istendiğinde hangi
kapsamın ne istediği, döngüsel bağımlılıkta ise zincirin tamamı bildirilir. Dolaylılık
arttıkça hatanın kendini açıklaması zorunlu hâle gelir.

#### Karar Yüzeyi

Bütünleşikliğin kazancı, tek tek özelliklerde değil karar sayısındadır. Bu sayılabilir.

```js
// karar-yuzeyi.mjs — butunlesikligin karar ve uyum yuzeyine etkisi
const ROLLER = [
  "görünüm katmanı", "yönlendirme", "form modeli", "veri getirme",
  "bağımlılık çözümü", "paylaşılan durum", "sunucuda oluşturma", "sınama araçları",
];

// Bir profil: her rolu hangi paketin karsiladigi.
const profiller = {
  "kitaplık toplama": ROLLER.map((r) => r),                       // her rol ayri paket
  "çekirdek + eklenti": ROLLER.map((r, i) => (i < 3 ? "çekirdek" : r)),
  "tam kapsamlı": ROLLER.map(() => "çatı"),
};

const ikili = (m) => (m * (m - 1)) / 2;

console.log("profil               bağımsız paket  sürüm akışı  ikili uyum  kırıcı değişiklikte  tek seferde");
console.log("                                                    ilişkisi   gözden geçirilecek  değişecek rol");
for (const [ad, atama] of Object.entries(profiller)) {
  const paketler = [...new Set(atama)];
  const m = paketler.length;
  // En cok rol tasiyan paket birakilirsa kac rol birden degisir?
  const enBuyuk = Math.max(...paketler.map((p) => atama.filter((a) => a === p).length));
  console.log(
    `${ad.padEnd(20)} ${String(m).padStart(14)} ${String(m).padStart(12)} ${String(ikili(m)).padStart(11)} ` +
    `${String(Math.max(m - 1, 0)).padStart(20)} ${String(enBuyuk).padStart(14)}`
  );
}

// Ayni hesap, rol sayisi degistikce: uyum yuzeyi kare buyur.
console.log("\nayrı paket sayısı  ikili uyum ilişkisi");
for (const m of [1, 2, 4, 6, 8, 12]) console.log(`${String(m).padStart(17)} ${String(ikili(m)).padStart(20)}`);

// Bir rolun catidan cikarilabilirligi: catinin o rolu ne kadar sikica bagladigi.
const BAGLILIK = { "sözleşme yok": 0, "arayüz üzerinden": 1, "doğrudan tip bağımlılığı": 2, "kod üretimi": 3 };
const ornek = [
  ["yönlendirme", "kod üretimi"],
  ["form modeli", "doğrudan tip bağımlılığı"],
  ["veri getirme", "arayüz üzerinden"],
  ["paylaşılan durum", "sözleşme yok"],
];
console.log("\nrol                bağlılık biçimi            değiştirme puanı");
let toplam = 0;
for (const [rol, bicim] of ornek) {
  toplam += BAGLILIK[bicim];
  console.log(`${rol.padEnd(18)} ${bicim.padEnd(26)} ${String(BAGLILIK[bicim]).padStart(17)}`);
}
console.log(`toplam çıkış maliyeti (0 = serbest, ${ornek.length * 3} = tümüyle bağlı): ${toplam}`);
```

```
profil               bağımsız paket  sürüm akışı  ikili uyum  kırıcı değişiklikte  tek seferde
                                                    ilişkisi   gözden geçirilecek  değişecek rol
kitaplık toplama                  8            8          28                    7              1
çekirdek + eklenti                6            6          15                    5              3
tam kapsamlı                      1            1           0                    0              8

ayrı paket sayısı  ikili uyum ilişkisi
                1                    0
                2                    1
                4                    6
                6                   15
                8                   28
               12                   66

rol                bağlılık biçimi            değiştirme puanı
yönlendirme        kod üretimi                                3
form modeli        doğrudan tip bağımlılığı                   2
veri getirme       arayüz üzerinden                           1
paylaşılan durum   sözleşme yok                               0
toplam çıkış maliyeti (0 = serbest, 12 = tümüyle bağlı): 6
```

Sekiz rolü ayrı paketlerle karşılamak sekiz karar, sekiz bağımsız sürüm akışı ve 28 ikili
uyum ilişkisi demektir. İlişki sayısı paket sayısının karesiyle büyür: on iki pakette 66
olur. Bu ilişkilerin her biri, iki paketin belirli sürümlerinin birlikte çalıştığı iddiasıdır
ve bu iddiayı doğrulayan taraf ekiptir.

Tam kapsamlı profilde bu sayı sıfırdır. Parçaların uyumu çatının sorumluluğudur; ekip tek
bir sürüm akışı izler. Yeni bir ekip üyesinin öğreneceği şey sekiz kitaplığın birleşimi
değil, bir sözleşmedir.

Son sütun bedeli gösteriyor. Kitaplık toplama profilinde bir paket bırakıldığında yalnızca
bir rol değişir; roller ayrı ayrı, ayrı zamanlarda göç edebilir. Tam kapsamlı profilde çatı
bırakılırsa sekiz rol aynı anda değişir. Karar geri alınabilir olmaktan çıkar.

#### Sözleşme Birliğinin Bedeli

Son tablo bu bedelin nasıl ölçüleceğini gösteriyor. Bir rolün çatıdan çıkarılabilirliği,
çatının o rolü hangi biçimde bağladığına göre değişir. Yalnızca bir arayüz üzerinden
bağlanan bir rol, aynı arayüzü karşılayan başka bir gerçekleştirimle değiştirilebilir.
Kod üretimiyle bağlanan bir rol, kaynak metnin biçimine kadar çatıya yaslanır.

Uygulamanın çıkış maliyetini ölçmek, çatı seçimini bir tercih olmaktan çıkarıp bir yatırım
kararına dönüştürür. Ölçü, uygulamanın hangi bölümlerinin çatıdan bağımsız yazılabildiğidir:
doğrulama kuralları, biçimlendirme, başsız bileşenlerdeki durum makineleri ve saf sunum
bileşenleri çatı bilmez ve göçte olduğu gibi taşınır.

İkinci bedel sürüm yönetimidir. Tek bir sürüm akışı, çatının bir sürüm yükseltmesinin
uygulamanın **her** katmanını aynı anda ilgilendirmesi demektir. Kırıcı bir değişiklik
yönlendirmeyi de, formu da, bağımlılık çözümünü de kapsayabilir; yükseltme küçük adımlara
bölünemez. Ayrı paketlerde ise yükseltmeler sıraya konabilir, ama karşılığında 28 uyum
ilişkisi taşınır.

Üçüncü bedel esnekliktir. Bir rolde çatının verdiği yanıt uygulamanın ihtiyacına uymuyorsa,
o rolü değiştirmek çatının varsayımlarına karşı çalışmak anlamına gelir. Bütünleşik bir
çerçevede alışılmışın dışına çıkmak, alışılmışın içinde kalmaktan belirgin biçimde pahalıdır.

#### Ailenin Profili

Bu aile bir güncelleme modeli değil, bir **kapsam** kararıdır; içinde sanal ağaç da,
çalışma zamanı bağımlılık takibi de, derleme zamanı çözümleme de bulunabilir. Ayırt edici
özelliği, bileşen katmanının üstündeki rollerin çatının parçası olmasıdır.

Kazanç, kararların ve uyum ilişkilerinin sayıca azalması, ekip içinde tek bir sözleşmenin
paylaşılması ve yeni bir üyenin öğreneceği yüzeyin daralmasıdır. Bedel, sürüm akışının
tekleşmesi, yükseltmelerin bölünememesi ve çıkış maliyetinin tek seferde ödenmesidir.

#### Özet

- Tam kapsamlı çerçeve, görünüm katmanının yanında yönlendirme, form, veri getirme, bağımlılık
  çözümü ve sınama gibi rolleri tek sözleşmede toplar.
- Bağımlılık enjeksiyonunda bileşen servisi kurmaz, anahtarla ister; kapsam zinciri
  sağlayıcıyı bulur, kayıt biçimi örneğin nerede yaşayacağını belirler.
- Tekil kayıtlı bir bağımlılığı değiştirmek, örnek kurulduktan sonra etkisizdir; sınama için
  kapsam baştan kurulur.
- Karar yüzeyi sayılabilir: sekiz rol ayrı paketlerde sekiz sürüm akışı ve 28 ikili uyum
  ilişkisi üretir, bütünleşik çatıda bu sayı sıfıra iner.
- Karşılığında çıkış maliyeti tek seferde ödenir: çatı bırakıldığında sekiz rol aynı anda
  değişir.
- Bir rolün çıkarılabilirliği bağlılık biçimine bağlıdır; arayüz üzerinden bağlanan rol
  değiştirilebilir, kod üretimiyle bağlanan rol kaynak metne kadar yaslanır.

#### Sonraki Adım

Dört ailenin de özellikleri, maliyetleri ve bedelleri ortaya kondu. Geriye tek bir soru
kalıyor ve bu sorunun yanıtı ailelerin özelliklerinde değil, projenin kendisindedir: bu
uygulama için hangi özellikler gerçekten gerekli? Sonraki ders bir karşılaştırma tablosu
değil, bir karar çerçevesi kurar — ekibin büyüklüğü ve deneyimi, uygulamanın etkileşim
yoğunluğu, oluşturma stratejisi gereksinimi, ekosistem derinliği ve bakım maliyeti gibi
ölçütleri açık ağırlıklara bağlar; kararın hangi ölçüt değiştiğinde döndüğünü hesaplar ve
kararın ne kadarının geri alınabilir kaldığını ölçer.

### Çerçeve Seçimi

Source: https://academia.sh/tr/kurslar/bilesen-tabanli-gelistirme/cerceve-secimi

Ölçütlerin nereden okunacağı, ağırlıkların açık yazılması, sıralamanın duyarlılık çözümlemesiyle sınanması, kararın geri alınabilirliğinin satır düzeyinde ölçülmesi ve kararın yazılı hâle getirilmesi.

Dört ailenin de özellikleri, maliyetleri ve bedelleri ortaya kondu. Geriye tek bir soru
kalıyor ve bu sorunun yanıtı ailelerin özelliklerinde değil, projenin kendisindedir.

Bu ders bir karşılaştırma tablosu değildir. Böyle bir tablo, yazıldığı ay için bile
eksiktir; bir yıl sonra yanıltıcı olur. Yerine kurulan şey, ölçütleri açık yazan, ağırlıkları
görünür kılan ve kararın hangi varsayıma dayandığını söyleyen bir çerçevedir. Çerçeve
değişmez; girdileri her projede yeniden doldurulur.

#### Ölçütler Nereden Okunur

Seçim tartışmalarının çoğu, farklı türden bilgileri aynı listeye koymaktan çıkmaz hâle
gelir. Ölçütler üç kaynaktan gelir ve her birinin güvenilirliği farklıdır.

**Aileden türetilebilen ölçütler**, bu konudaki dört derste ölçüldü: güncelleme tanesi,
çalışma zamanına inen kodun niteliği, derleme adımı gereksinimi, elle belleme yükü, dinamik
yapılara uyum, güncellemenin bölünebilirliği, karar yüzeyinin genişliği. Bunlar ailenin
tanımından çıkar ve bir ürünün belgesine bakmadan bilinir.

**Adaydan okunan ölçütler** belirli bir çerçevenin kendisine bakılarak ölçülür: belgelendirmenin
niteliği, kırıcı değişiklik geçmişi, yayın düzeni, sorunların kapanma süresi, erişilebilirlik
desteğinin durumu. Bunlar aileden çıkmaz ve zamanla değişir; karar anında ölçülmeleri gerekir.

**Ekipten okunan ölçütler** ne aileye ne adaya aittir: ekibin önceki deneyimi, öğrenme
bütçesi, işe alım kolaylığı, projenin beklenen ömrü, bakımı kimin üstleneceği. Bunlar çoğu
zaman en ağır basan ölçütlerdir ve teknik tartışmada en az konuşulanlardır.

Aşağıdaki model yalnızca birinci gruptaki ölçütleri puanlar; ikinci ve üçüncü gruptakiler
ağırlık olarak değil, kararın kendisine giren bağımsız girdiler olarak ele alınır.

#### Ağırlıkları Açık Yazmak

```js
// secim.mjs — karar cercevesi: acik agirliklar ve duyarlilik cozumlemesi
// Puanlar (0-3) onceki derslerde olculen ozelliklerden gelir; urun degerlendirmesi degildir.

const GUNCELLEME_MODELI = {
  "sanal ağaç tutan": {
    "ince taneli güncelleme": 1, "çalışma zamanı kodunun azlığı": 1, "derleme adımı gerektirmemesi": 3,
    "elle belleme gerektirmemesi": 0, "dinamik yapılara uyum": 3, "güncellemenin bölünebilirliği": 3,
  },
  "çalışma zamanında bağımlılık izleyen": {
    "ince taneli güncelleme": 3, "çalışma zamanı kodunun azlığı": 2, "derleme adımı gerektirmemesi": 2,
    "elle belleme gerektirmemesi": 3, "dinamik yapılara uyum": 3, "güncellemenin bölünebilirliği": 1,
  },
  "derleme zamanında tepkiselleştiren": {
    "ince taneli güncelleme": 3, "çalışma zamanı kodunun azlığı": 3, "derleme adımı gerektirmemesi": 0,
    "elle belleme gerektirmemesi": 3, "dinamik yapılara uyum": 1, "güncellemenin bölünebilirliği": 1,
  },
};

const KAPSAM = {
  "dar kapsamlı": { "karar yüzeyinin küçüklüğü": 0, "yükseltmenin bölünebilirliği": 3, "çıkışın kademeli olması": 3, "tek sözleşme": 1 },
  "bütünleşik": { "karar yüzeyinin küçüklüğü": 3, "yükseltmenin bölünebilirliği": 0, "çıkışın kademeli olması": 0, "tek sözleşme": 3 },
};

const PROJELER = {
  "kamuya açık istasyon sayfası": {
    "ince taneli güncelleme": 1, "çalışma zamanı kodunun azlığı": 5, "derleme adımı gerektirmemesi": 1,
    "elle belleme gerektirmemesi": 2, "dinamik yapılara uyum": 1, "güncellemenin bölünebilirliği": 1,
    "karar yüzeyinin küçüklüğü": 2, "yükseltmenin bölünebilirliği": 2, "çıkışın kademeli olması": 3, "tek sözleşme": 1,
  },
  "iç gözlem panosu": {
    "ince taneli güncelleme": 5, "çalışma zamanı kodunun azlığı": 1, "derleme adımı gerektirmemesi": 1,
    "elle belleme gerektirmemesi": 3, "dinamik yapılara uyum": 3, "güncellemenin bölünebilirliği": 2,
    "karar yüzeyinin küçüklüğü": 4, "yükseltmenin bölünebilirliği": 1, "çıkışın kademeli olması": 1, "tek sözleşme": 4,
  },
};

const puan = (adaylar, agirlik) =>
  Object.entries(adaylar).map(([ad, ozellikler]) => [
    ad,
    Object.entries(ozellikler).reduce((t, [olcut, p]) => t + (agirlik[olcut] ?? 0) * p, 0),
  ]).sort((a, b) => b[1] - a[1]);

for (const [proje, agirlik] of Object.entries(PROJELER)) {
  console.log(`\n=== ${proje} ===`);
  for (const [baslik, adaylar] of [["güncelleme modeli", GUNCELLEME_MODELI], ["kapsam", KAPSAM]]) {
    const siralama = puan(adaylar, agirlik);
    console.log(`  ${baslik}:`);
    for (const [ad, p] of siralama) console.log(`    ${ad.padEnd(38)} ${String(p).padStart(4)}`);
    console.log(`    fark (birinci − ikinci): ${siralama[0][1] - siralama[1][1]}`);
  }
}

// Duyarlilik: bir olcutun agirligi kac olursa siralamanin basi degisir?
console.log("\n=== duyarlılık: güncelleme modeli, iç gözlem panosu ===");
const taban = { ...PROJELER["iç gözlem panosu"] };
const kazanan = (a) => puan(GUNCELLEME_MODELI, a)[0][0];
const tabanKazanan = kazanan(taban);
console.log(`taban ağırlıklarla ilk sıra: ${tabanKazanan}`);
console.log("ölçüt                                taban  dönüş ağırlığı  yeni ilk sıra");
for (const olcut of Object.keys(GUNCELLEME_MODELI["sanal ağaç tutan"])) {
  let donus = null, yeni = null;
  for (let w = 0; w <= 20; w++) {
    const deneme = { ...taban, [olcut]: w };
    if (kazanan(deneme) !== tabanKazanan) { donus = w; yeni = kazanan(deneme); break; }
  }
  console.log(
    `${olcut.padEnd(36)} ${String(taban[olcut]).padStart(5)} ` +
    `${(donus === null ? "dönmüyor" : String(donus)).padStart(15)}  ${yeni ?? "-"}`
  );
}
```

```

=== kamuya açık istasyon sayfası ===
  güncelleme modeli:
    derleme zamanında tepkiselleştiren       26
    çalışma zamanında bağımlılık izleyen     25
    sanal ağaç tutan                         15
    fark (birinci − ikinci): 1
  kapsam:
    dar kapsamlı                             16
    bütünleşik                                9
    fark (birinci − ikinci): 7

=== iç gözlem panosu ===
  güncelleme modeli:
    çalışma zamanında bağımlılık izleyen     39
    derleme zamanında tepkiselleştiren       32
    sanal ağaç tutan                         24
    fark (birinci − ikinci): 7
  kapsam:
    bütünleşik                               24
    dar kapsamlı                             10
    fark (birinci − ikinci): 14

=== duyarlılık: güncelleme modeli, iç gözlem panosu ===
taban ağırlıklarla ilk sıra: çalışma zamanında bağımlılık izleyen
ölçüt                                taban  dönüş ağırlığı  yeni ilk sıra
ince taneli güncelleme                   5        dönmüyor  -
çalışma zamanı kodunun azlığı            1               9  derleme zamanında tepkiselleştiren
derleme adımı gerektirmemesi             1              16  sanal ağaç tutan
elle belleme gerektirmemesi              3        dönmüyor  -
dinamik yapılara uyum                    3        dönmüyor  -
güncellemenin bölünebilirliği            2              10  sanal ağaç tutan
```

İki proje aynı puan tablosundan farklı sıralamalar üretiyor. Ayrım ailelerden değil,
ağırlıklardan geliyor: kamuya açık sayfada indirilen kodun azlığı beş kat ağırlıkta,
gözlem panosunda ince tanelilik beş kat ağırlıkta.

Kapsam ekseninde sıralama daha keskin ayrılıyor. Kamuya açık sayfada uzun ömür ve kademeli
çıkış öne çıktığı için dar kapsam önde; iç panoda karar yüzeyinin küçüklüğü ve tek sözleşme
ağır bastığı için bütünleşik profil önde. İki eksen bağımsızdır ve ayrı ayrı kararlaştırılır.

#### Sıralama Değil, Fark Önemlidir

Birinci projede birinci ile ikinci arasındaki fark **1 puan**. Bu, modelin o proje için bir
yanıt vermediği anlamına gelir. Puanların girdisi tahminî ağırlıklardır; bir puanlık fark,
ağırlıklardaki en küçük değişiklikle kaybolur. Böyle bir sonuçta doğru okuma "birinci
kazandı" değil, "**bu iki seçenek bu ölçütlerle ayrılmıyor**" olmalıdır. Karar, modelin
kapsamadığı ölçütlere — adaydan ve ekipten okunanlara — bırakılır.

İkinci projede fark 7 puan ve duyarlılık tablosu bunun ne kadar sağlam olduğunu gösteriyor.
En ağır ölçütün ağırlığı yirmiye kadar çıkarıldığında bile ilk sıra değişmiyor; buna karşılık
çalışma zamanı kodunun azlığı 1'den 9'a çıkarsa sıralama dönüyor. Bu, kararın hangi
varsayıma dayandığını söyler: "indirilen kod miktarı bu proje için ikincil" varsayımı
düşerse karar da düşer.

Duyarlılık çözümlemesinin asıl işlevi budur. Bir sayı üretmez; **kararın kırılma noktasını**
yazılı hâle getirir. Kırılma noktası bilindiğinde, proje koşulları değiştiğinde kararın
gözden geçirilmesi gerekip gerekmediği tartışmasız olur.

#### Kararın Geri Alınabilirliği

Seçimin son ölçütü, seçimin yanlış çıkması durumunda ne olacağıdır. Bu da sayılabilir.

```js
// goc.mjs — kararin ne kadarinin geri alinabilir kaldigini olcmek
// Baglilik: 0 = catidan bagimsiz, 1 = arayuz uzerinden, 2 = dogrudan bagli.
const AYRIM_YAPILMIS = [
  { ad: "eşik ve tarih doğrulama kuralları", satir: 180, baglilik: 0 },
  { ad: "ölçüm biçimlendirme ve birim çevrimi", satir: 120, baglilik: 0 },
  { ad: "seçenek listesi durum makinesi", satir: 260, baglilik: 0 },
  { ad: "erişilebilirlik öznitelik türetimi", satir: 90, baglilik: 0 },
  { ad: "veri erişim katmanı", satir: 310, baglilik: 1 },
  { ad: "sunum bileşenleri (işaretleme)", satir: 940, baglilik: 1 },
  { ad: "konteynerler ve kancalar", satir: 380, baglilik: 2 },
  { ad: "yönlendirme ve form bağlama", satir: 360, baglilik: 2 },
];

// Ayni uygulama, ayrimlar yapilmadan: mantik bilesenlerin govdesinde.
const AYRIM_YAPILMAMIS = [
  { ad: "bileşenler (mantık gövdede)", satir: 2210, baglilik: 2 },
  { ad: "veri erişim katmanı", satir: 310, baglilik: 1 },
  { ad: "yönlendirme ve form bağlama", satir: 360, baglilik: 2 },
];

function olc(ad, moduller) {
  const toplam = moduller.reduce((t, m) => t + m.satir, 0);
  const grup = (b) => moduller.filter((m) => m.baglilik === b).reduce((t, m) => t + m.satir, 0);
  const bagimsiz = grup(0), arayuz = grup(1), dogrudan = grup(2);
  // Gocte yeniden yazilacak: dogrudan bagli olanin tamami, arayuz uzerinden baglinin yarisi.
  const yenidenYazilacak = dogrudan + Math.round(arayuz / 2);
  console.log(`\n--- ${ad} ---`);
  console.log("modül                                    satır  bağlılık");
  for (const m of moduller) console.log(`${m.ad.padEnd(40)} ${String(m.satir).padStart(6)}  ${m.baglilik}`);
  console.log(`toplam ${toplam} satır | bağımsız ${bagimsiz} (%${((bagimsiz / toplam) * 100).toFixed(1)})` +
    ` | arayüz üzerinden ${arayuz} | doğrudan bağlı ${dogrudan}`);
  console.log(`göçte yeniden yazılacak: ${yenidenYazilacak} satır (%${((yenidenYazilacak / toplam) * 100).toFixed(1)})`);
  return yenidenYazilacak;
}

const a = olc("ayrımlar yapılmış", AYRIM_YAPILMIS);
const b = olc("ayrımlar yapılmamış", AYRIM_YAPILMAMIS);
console.log(`\nayrımların göçte kazandırdığı: ${b - a} satır`);
```

```

--- ayrımlar yapılmış ---
modül                                    satır  bağlılık
eşik ve tarih doğrulama kuralları           180  0
ölçüm biçimlendirme ve birim çevrimi        120  0
seçenek listesi durum makinesi              260  0
erişilebilirlik öznitelik türetimi           90  0
veri erişim katmanı                         310  1
sunum bileşenleri (işaretleme)              940  1
konteynerler ve kancalar                    380  2
yönlendirme ve form bağlama                 360  2
toplam 2640 satır | bağımsız 650 (%24.6) | arayüz üzerinden 1250 | doğrudan bağlı 740
göçte yeniden yazılacak: 1365 satır (%51.7)

--- ayrımlar yapılmamış ---
modül                                    satır  bağlılık
bileşenler (mantık gövdede)                2210  2
veri erişim katmanı                         310  1
yönlendirme ve form bağlama                 360  2
toplam 2880 satır | bağımsız 0 (%0.0) | arayüz üzerinden 310 | doğrudan bağlı 2570
göçte yeniden yazılacak: 2725 satır (%94.6)

ayrımların göçte kazandırdığı: 1360 satır
```

Aynı uygulama, iki farklı iç düzenle. Ayrımlar yapıldığında kodun dörtte biri çatıdan
tümüyle bağımsız; göçte yeniden yazılacak bölüm yarıya iniyor. Ayrımlar yapılmadığında
bağımsız kod sıfır ve uygulamanın neredeyse tamamı yeniden yazılıyor.

Bağımsız kalan dört modül tesadüf değil: doğrulama kuralları, biçimlendirme, durum makinesi
ve erişilebilirlik öznitelik türetimi. Dördü de önceki konudaki ayrımların ürünü — sunum ile
konteynerin ayrılması, davranışın başsız bir çekirdeğe alınması, mantığın kancalara
taşınması. Bu ayrımların değeri okunabilirlikle sınırlı değildir; çerçeve kararını geri
alınabilir tutan şey de onlardır.

Buradan seçim tartışmasını yumuşatan bir sonuç çıkar. Bağımsız kod oranı yüksek tutulduğunda,
çerçeve kararı bir kader değil bir tercihtir. Kararın ağırlığı, kararın geri alınabilirliğiyle
ters orantılıdır.

#### Kararın Yazılı Hâli

Karar çerçevesinin çıktısı bir çerçeve adı değil, bir kayıttır. Kaydın taşıması gereken beş
şey vardır.

Bağlam: uygulamanın ne olduğu, beklenen ömrü, ekibin büyüklüğü ve deneyimi. Ölçütler ve
ağırlıklar: hangi ölçütün neden ağır bastığı, sayıyla. Değerlendirilen adaylar ve elenme
gerekçeleri. Kararın dayandığı varsayımlar ve duyarlılık çözümlemesinden çıkan kırılma
noktaları. Gözden geçirme koşulu: hangi olay olursa kararın yeniden ele alınacağı.

Bu kaydın en çok işe yarayan bölümü sonuncusudur. "Şu koşul değişirse yeniden bakılacak"
cümlesi, kararı hem savunulabilir hem de değiştirilebilir kılar; kaydı olmayan bir karar
zamanla gerekçesini yitirir ve yalnızca alışkanlık olarak sürer.

#### Özet

- Ölçütler üç kaynaktan gelir: aileden türetilebilenler, adaydan okunanlar ve ekipten
  okunanlar. Yalnızca birincisi ailenin tanımından çıkar.
- Ağırlıklar açık yazıldığında aynı puan tablosu farklı projelerde farklı sıralama üretir;
  ayrım ailelerden değil ağırlıklardan gelir.
- Güncelleme modeli ile kapsam iki bağımsız eksendir ve ayrı ayrı kararlaştırılır.
- Sıralamanın kendisi değil, birinci ile ikinci arasındaki fark anlamlıdır; bir puanlık fark
  "ayrılmıyor" demektir ve duyarlılık çözümlemesi kararın kırılma noktasını yazılı hâle
  getirir.
- Kararın geri alınabilirliği ölçülebilir; ayrımlar yapıldığında göçte yeniden yazılacak
  bölüm %94,6'dan %51,7'ye indi.
- Çıktı bir çerçeve adı değil, gözden geçirme koşulunu da içeren yazılı bir karar kaydıdır.

#### Kurs Kapanışı

Bu kurs, elle DOM yönetiminin ölçek sorunlarıyla başladı ve durumdan görünüme tek yönlü bir
akış kurdu. Bileşen, girdi özellikleri, çıktı olayları ve yerel durumdan oluşan üç kanallı
bir sözleşme olarak tanımlandı; yaşam döngüsü, yan etkiler, türetilmiş değerler, bağlam ve
hata sınırlarıyla tek bir bileşen sonuna kadar götürüldü.

İkinci yarıda bileşenler birleştirildi. Sarmalama ile yuva tabanlı bileşim arasındaki seçim,
özellik zinciri ve seçenek patlaması sayılarak gerekçelendirildi. Mantık kancalara ve
birleştirilebilir işlevlere taşındı; birlikte çalışan bileşen aileleri kuruldu; veri, davranış
ve görünüm üç ayrı katmana ayrıldı. Bileşenin dış yüzeyi — özellik yayılması, varsayılan
birleştirmesi ve denetim sahipliği — bir sözleşme olarak tasarlandı.

Son konu, bu bileşenlerin altındaki güncelleme düzeneklerini ele aldı. Aynı iki yüz satırlık
ölçüm tablosunda aynı değişiklik, ailelere göre bin altı yüz karşılaştırmadan tek bir yazma
işlemine kadar değişen iş miktarları üretti. Dört ailenin özellikleri, bedelleri ve karar
yüzeyleri sayılarla ortaya kondu ve bu ölçümler, ürün adı geçmeyen bir karar çerçevesine
dönüştürüldü.

Kursun bıraktığı yerde bir eksik var. Buraya kadar her şey tek bir bileşen ağacının
içindeydi: durum bir bileşende doğuyor, ağaç boyunca aşağı akıyor ve orada tüketiliyor. Oysa
istasyon uygulaması tek bir ekran değil. Ölçüm tablosu bir adreste, arşiv görünümü başka
bir adreste; süzgeç tercihi iki ekran arasında korunmalı; ölçüm verisi sunucudan geliyor ve
tarayıcıdaki kopyası ne zaman tazeleneceğini bilmiyor. Bunların hiçbiri bileşen bileşimiyle
çözülmez; bileşenlerin **üstündeki** katmana aittir.

Bir sonraki kurs — **Uygulama Mimarisi: Yönlendirme, Durum ve Veri** — bu katmanı kurar.
Adresin bir görünüme nasıl eşlendiğini ve iç içe düzenlerin ortak kabuğu nasıl paylaştığını,
yerel durum ile paylaşılan durumun ve sunucu durumunun neden ayrı yönetildiğini, sunucudan
gelen verinin önbelleklenmesini ve geçersizleştirilmesini, form durumunun ve doğrulama
şemalarının paylaşılmasını, tarayıcıda kimlik doğrulamanın seçeneklerini ve risklerini ele
alır. Bu kursta kurulan bileşim ve yüzey tasarımı alışkanlıkları orada da geçerlidir: durumun
sahibi, sözleşmenin sınırı ve kararın geri alınabilirliği aynı sorulardır, yalnızca ölçek
büyür.

