İçeriğe geç
academia.sh

Ders 18 / 25

Sunum ve Konteyner Ayrımı

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.

İçindekiler

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

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

İ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