İçeriğe geç
academia.sh

Ders 03 / 25

Bileşen Kavramı

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.

İçindekiler

Ö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, 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.

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

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

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

Notlarım

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

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat