---
title: 'Bildirimsel Oluşturma'
source: 'https://academia.sh/tr/kurslar/bilesen-tabanli-gelistirme/bildirimsel-olusturma'
course: 'Bileşen Tabanlı Arayüz Geliştirme'
language: tr
updated: '2026-08-17T18:09:13+00:00'
license: 'CC BY-SA 4.0'
---

# Bildirimsel Oluşturma

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.
