İçeriğe geç
academia.sh

Ders 20 / 25

Bileşen API Tasarımı

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

İçindekiler

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.

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

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

İ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