Ders 03 / 20
Çekirdek Bileşenler
Çekirdek bileşenler görünüm, metin, görsel ve giriş alanıdır; hepsi görünüm ağacında bir düğümdür. Ders yer tutucu metnin görselle değiştiği güncellemede tip değişiminin ağaca ne yaptığını ölçer.
Yayın Güncelleme
Bu derste
- Görünüm, metin, görsel ve giriş alanını ağaçta tipli birer düğüm olarak tanır
- Aynı tipte özellik değişiminin düğümü güncellenen yaptığını ölçer
- Tip değişince eski düğümün sökülen, yenisinin kurulan çıktığını ve özelliklere bakılmadığını gösterir
- Verinin ağaçtaki yolu ile verinin vardığı düğümdeki akıbeti birbirinden ayırır
İçindekiler
Önceki ders ilk ağacın nereden geldiğini sordu ve ana ekranı bütün alt ağacıyla kurulan buldu. Bu ders o ana ekranın içine bakar. Bir ekran, çerçevenin hazır sunduğu birkaç çekirdek tipten kurulur: kapsayıcı bir görünüm, metin, görsel ve yazı girilen bir alan. Hepsi ağaçta bir düğümdür ve düğümün tipi, akıbet kuralının üçüncü sorusudur: aynı yerde, ama başka tipte bir düğüm gelirse eskisi sökülür, yenisi kurulur.
Aşağıdaki parça bir profil kartının tarifidir. Parça ölçülmez; model çerçevenin kodunu çalıştırmaz.
// Platform kodu: Profile
function Profile({ name, photo }) {
return (
<View>
{photo ? <Image source={photo} /> : <Text>Yukleniyor</Text>}
<Text>{name}</Text>
<TextInput value={name} />
</View>
);
}
View bir kapsayıcıdır ve kendi başına bir şey göstermez; çocuklarını bir arada tutar. Text yazıyı gösterir. Image bir görsel kaynağından çizilir. TextInput kullanıcının yazı girdiği alandır. İşlev, görsel hazırsa Image, değilse bir yer tutucu Text döndürür; ad ise her iki durumda da bir Text olarak gelir. Bu dört tip, bu derste ele alınan çekirdek bileşenlerdir; sonraki derslerde dokunma ve platform seçimiyle yenileri eklenir.
Dört Tip, Bir Kural
Çekirdek tiplerin her biri yerel tarafta bir görünüme karşılık gelir. Ortak kod Text düğümü tarif ettiğinde iOS’ta ve Android’de platformun kendi yazı görünümü kurulur. Yerel görünüm düğümün tipine bağlıdır; tip değişirse yerel görünüm de değişmek zorundadır. Bir yazı görünümü, yerinde bir görsel görünümüne dönüşemez. Bu yüzden kuralın üçüncü sorusu tipe bakar ve tip farklıysa özelliklere bakmaz: eski alt ağaç sökülür, yeni alt ağaç kurulur.
Ölçüm için önce çekirdek gerekir. Karşılaştırma çekirdeği her derste aynı biçimde yeniden verilir.
// fate.mjs — bir guncellemenin gorunum agacindaki her dugume ne yaptigini siniflandirir export const FATES = ["korunan", "guncellenen", "kurulan", "sokulen"]; export function el(type, props = {}, children = [], key = null) { return { type, props, children, key }; } function segment(node) { return node.key === null ? node.type : `${node.type}[${node.key}]`; } function identity(node, index) { return node.key === null ? `#${index}` : `key:${node.key}`; } function sameProps(a, b) { const names = [...new Set([...Object.keys(a), ...Object.keys(b)])].sort(); return names.every((n) => JSON.stringify(a[n]) === JSON.stringify(b[n])); } function whole(node, path, fate, ops) { const here = `${path}/${segment(node)}`; ops.push({ fate, path: here }); node.children.forEach((child) => whole(child, here, fate, ops)); } export function reconcile(prev, next, path = "", ops = []) { if (prev === null) { whole(next, path, "kurulan", ops); return ops; } if (next === null) { whole(prev, path, "sokulen", ops); return ops; } if (prev.type !== next.type || prev.key !== next.key) { whole(prev, path, "sokulen", ops); whole(next, path, "kurulan", ops); return ops; } const here = `${path}/${segment(next)}`; ops.push({ fate: sameProps(prev.props, next.props) ? "korunan" : "guncellenen", path: here }); const before = new Map(prev.children.map((child, i) => [identity(child, i), child])); const matched = new Set(); next.children.forEach((child, i) => { const id = identity(child, i); matched.add(id); reconcile(before.has(id) ? before.get(id) : null, child, here, ops); }); prev.children.forEach((child, i) => { if (!matched.has(identity(child, i))) reconcile(child, null, here, ops); }); return ops; } export function printOps(ops) { ops.forEach((op) => console.log(`${op.fate}: ${op.path}`)); } export function countFates(ops) { for (const name of FATES) { console.log(`${name}: ${ops.filter((op) => op.fate === name).length}`); } console.log(`komut: ${ops.filter((op) => op.fate !== "korunan").length}`); }
Bu dersin yardımcı dosyası, profil durumundan görünüm ağacı üretir. Kart bir kapsayıcıdır; ilk çocuğu görsel hazırsa Image, değilse yer tutucu Text’tir. Adı taşıyan metnin anahtarı addır, bu yüzden yeri kardeşlerinin değişiminden etkilenmez. Giriş alanı biyografiyi taşır. İkinci işlev, yer tutucuyu da bir görsel yapan alternatif bir tariftir; aşağıda karşılaştırma için kullanılır. Görselin erişilebilirlik etiketi adı taşır; ad bu yüzden kartta iki yerde görünebilir.
// profile.mjs — profil durumundan gorunum agaci uretir import { el } from "./fate.mjs"; export function renderProfile(state) { const slot = state.photo === null ? el("Text", { value: "Yukleniyor" }) : el("Image", { source: state.photo, label: state.name }); return el("Card", {}, [ slot, el("Text", { value: state.name }, [], "ad"), el("Input", { value: state.bio }), ]); } export function renderProfileImage(state) { return el("Card", {}, [ el("Image", { source: state.photo, label: state.name }), el("Text", { value: state.name }, [], "ad"), el("Input", { value: state.bio }), ]); }
Aynı Tip, Değişen Özellik
İlk ölçümde kullanıcı adını değiştirir. Görsel henüz yüklenmemiştir; yer tutucu durur. Tip ve anahtar her düğümde aynı kaldığı için kural özelliklere bakar.
// main.mjs import { reconcile, printOps, countFates } from "./fate.mjs"; import { renderProfile } from "./profile.mjs"; const before = renderProfile({ name: "Ayse", photo: null, bio: "Kahve sever" }); const after = renderProfile({ name: "Ayse Yilmaz", photo: null, bio: "Kahve sever" }); const ops = reconcile(before, after); printOps(ops); countFates(ops);
korunan: /Card korunan: /Card/Text guncellenen: /Card/Text[ad] korunan: /Card/Input korunan: 3 guncellenen: 1 kurulan: 0 sokulen: 0 komut: 1
Dört düğümün üçü korunan, biri güncellenen çıktı; komut birdir. /Card/Text[ad] aynı yerde, aynı tip ve aynı anahtarla durdu ama value özelliği değişti, bu yüzden güncellenen oldu. Yer tutucu Text ve giriş alanı değişmedi ve yerel tarafa hiçbir komut gitmedi.
Veri Nereye Varıyor
Aynı ad değişikliğini, görsel yüklenmişken yapalım. Ad artık iki yere ulaşır: yazı düğümüne ve görselin etiketine.
// main.mjs import { reconcile, printOps, countFates } from "./fate.mjs"; import { renderProfile } from "./profile.mjs"; const before = renderProfile({ name: "Ayse", photo: "profil.png", bio: "Kahve sever" }); const after = renderProfile({ name: "Ayse Yilmaz", photo: "profil.png", bio: "Kahve sever" }); const ops = reconcile(before, after); printOps(ops); countFates(ops);
korunan: /Card guncellenen: /Card/Image guncellenen: /Card/Text[ad] korunan: /Card/Input korunan: 2 guncellenen: 2 kurulan: 0 sokulen: 0 komut: 2
İlk ölçümde aynı değişiklik tek bir düğümü güncelledi, burada iki düğümü. Veri aynı, tek durum alanı. Ağaçtaki yolu da aynıdır: kökte tutulan durumdan kartın çocuklarına, aşağı doğru. Değişen şey verinin vardığı yerde ne olduğudur. Yer tutucu olduğunda ad yalnız yazı düğümüne ulaşıyordu; görsel olduğunda görselin etiketine de ulaşıyor.
Bu, M31/K03’te sorduğumuz “bu veri arayüz ağacında hangi yöne gidiyor” sorusundan ayrıdır. Orada verinin ağaçtaki yönü sınıflanıyordu: aşağı mı, yukarı mı. Bu kurs verinin yolunu değil, vardığı düğümde ne olduğunu sınıflar. Yönü aynı iki veri, bir düğümü güncelleyebilir ya da hiç etkilemeyebilir; yönü farklı iki veri aynı akıbeti de verebilir.
TY4. Model girişi kontrollü sayar: giriş alanının değeri ağaçta bir özelliktir. Gerçek bir giriş alanı yazılan metni yerel tarafta da tutar; yazı ile ağaçtaki değer ayrışırsa bunu kural görmez. Bu, ilk dersteki sınırın bir uygulamasıdır: ağaçta görünmeyen şey akıbet almaz.
Tip Değişince
Şimdi görsel yüklenir ve yer tutucunun yerine geçer. Kartın ilk çocuğu aynı yerde durur ama tipi Text’ten Image’a döner. Kural üçüncü soruda durur: tip farklıdır, özelliklere bakılmaz.
Parçayı Satır Satır Okumak
Başta verilen profil kartı parçasına bir kez daha dönelim. Profile işlevi iki girdi alır: name ve photo. Dönen ağacın kökü View’dır; çocukları sırayla bir görsel ya da yer tutucu, bir metin ve bir giriş alanıdır. Üçlü işleç, photo doluysa Image, değilse Text seçer. İşlev bir koşul içerir ama ağaç koşulu taşımaz: ağaçta yalnız seçimin sonucu bulunur. Bir önceki tarif ile bir sonraki tarif arasındaki fark, bu koşulun hangi dalının seçildiğine göre okunur. TextInput’a verilen value özelliği ise girişin içeriğidir; kullanıcının yazdığı ile ağaçta duran aynı olmak zorunda değildir, bu da yukarıdaki varsayımda söylenir.
Burada bir yanılgıyı ayıklayalım: “Görsel yüklenince yer tutucu ile görsel aynı düğümdür, sadece içeriği değişir.” Okurun gözünde ikisi kartın aynı köşesinde durur ve aynı şeyi temsil eder. Kuralın gözünde ise bir Text ile bir Image iki ayrı şeydir: yerel tarafta biri yazı görünümü, öbürü görsel görünümüdür ve birini diğerine dönüştürmek mümkün değildir. Düğümün ağaçtaki konumu aynı olsa da kimliği tipiyle birlikte gelir.
Yer Tutucu Da Bir Görsel Olsaydı
Karşı örnek için tarifi değiştirelim: yer tutucu da bir Image olsun, yalnız kaynağı boş olsun. Görsel gelince tip aynı kalır ve yalnız kaynak değişir.
// main.mjs import { reconcile, printOps, countFates } from "./fate.mjs"; import { renderProfileImage } from "./profile.mjs"; const before = renderProfileImage({ name: "Ayse", photo: null, bio: "Kahve sever" }); const after = renderProfileImage({ name: "Ayse", photo: "profil.png", bio: "Kahve sever" }); const ops = reconcile(before, after); printOps(ops); countFates(ops);
korunan: /Card guncellenen: /Card/Image korunan: /Card/Text[ad] korunan: /Card/Input korunan: 3 guncellenen: 1 kurulan: 0 sokulen: 0 komut: 1
Aynı kullanıcı için aynı değişim, bu tarifle tek komut. Yer tutucunun yerel görünümü sökülmedi; aynı görsel görünümü yeni kaynağı aldı. Kural tarifin içindeki niyeti bilmez, yalnız tipe ve özelliğe bakar; tarifi yazan kişi hangi tip değişiminin kaçınılabilir olduğunu görmek istiyorsa ölçüm ona bunu söyler. Her yer tutucunun görsel olabileceği söylenemez: bir yükleniyor yazısını bir görsele çevirmek anlam değiştirir. Ama tip değişiminin bir maliyeti olduğunu bilmek, tarifi yazarken seçim yapmayı mümkün kılar.
Sınırlayıcı Ölçüm
Odak düğüm, eski çocuk /Card/Text’tir. Görsel yüklenince kartın durumundaki photo değeri nulldan bir kaynağa döner ve ilk çocuk yer tutucu Text yerine Image olur.
// main.mjs import { reconcile, printOps, countFates } from "./fate.mjs"; import { renderProfile } from "./profile.mjs"; const before = renderProfile({ name: "Ayse", photo: null, bio: "Kahve sever" }); const after = renderProfile({ name: "Ayse", photo: "profil.png", bio: "Kahve sever" }); const ops = reconcile(before, after); printOps(ops); countFates(ops);
korunan: /Card sokulen: /Card/Text kurulan: /Card/Image korunan: /Card/Text[ad] korunan: /Card/Input korunan: 3 guncellenen: 0 kurulan: 1 sokulen: 1 komut: 2
/Card/Text sökülen, /Card/Image kurulan çıktı; komut iki. İki düğüm aynı konumda, ama tipleri farklı olduğu için kural onları aynı düğümün iki hâli saymadı. Yer tutucunun özelliği ile görselin özelliği arasında karşılaştırma yapılmadı. Ad metni anahtarı sayesinde kendi eşiyle buluştu ve korunan kaldı; yanındaki tip değişimi ona dokunmadı. Giriş alanı da korunan kaldı.
Sökülmenin bedeli yalnız komut sayısı değildir. Yer tutucu Text’in yerel görünümü ve içinde tuttuğu her durum yok olur; yeni Image hiçbir şeyi devralmaz. Yer tutucuda durum yoktu, bu yüzden burada bir şey kaybedilmedi. Aynı değişim bir giriş alanını bir yazı görünümüyle yer değiştirseydi, alandaki imleç konumu ve seçim yok olurdu. Bu kayıp ağaçta görünmez ve modelde ölçülmez; düzyazıda söylenir.
Şimdi karşılaştırma için bunun tersini yapalım: tip aynı kalsın, yalnız görselin kaynağı değişsin.
// main.mjs import { reconcile, printOps, countFates } from "./fate.mjs"; import { renderProfile } from "./profile.mjs"; const before = renderProfile({ name: "Ayse", photo: "profil.png", bio: "Kahve sever" }); const after = renderProfile({ name: "Ayse", photo: "profil2.png", bio: "Kahve sever" }); const ops = reconcile(before, after); printOps(ops.filter((op) => op.fate !== "korunan")); countFates(ops);
guncellenen: /Card/Image korunan: 3 guncellenen: 1 kurulan: 0 sokulen: 0 komut: 1
Aynı yerde, aynı tipte ve aynı anahtarla duran /Card/Image güncellenen çıktı; komut bir. Görselin yerel görünümü yerinde kaldı ve yeni kaynağı aldı. Tip değişimi ile özellik değişimi arasındaki fark komut sayısında görünür: ilki iki komut, ikincisi bir komut ve yerel görünümün kimliği yalnız ikincisinde yaşar. İki komutun birini ödemek istemiyorsak yer tutucuyu da Image yapabiliriz. Yer tutucu bir Image ise görsel gelince tip aynı kalır ve yalnız kaynak özelliği değişir. Bu bir tasarım seçimidir ve ağaçta nasıl göründüğü bu ölçümlerle okunur.
TY5. Model tip karşılaştırmasını ad üzerinden yapar. Gerçek çerçeve iki farklı tarif işlevini farklı sayar ve işlev kimliğini kullanır; model bu inceliği tip adına indirir. Model, bu akıbetin kuralını kurar; model çerçevenin kendisi değildir.
Bu ölçümlerin ortak dersi şudur: bir düğümün yaşamını belirleyen, ekranda nasıl göründüğü değil, tarifte hangi tiple ve hangi anahtarla yazıldığıdır. İki farklı görünüm aynı tipin farklı özellikleri olabilir; iki benzer görünüm farklı tipler olabilir. Tarifi yazan kişi tipi seçerken aslında şunu seçer: bu düğüm bir yazı görünümü olarak mı yaşayacak, bir görsel görünümü olarak mı? Yaşam boyu aynı kalacak olanı tek tipte tutmak, güncellemeleri ucuz yapar; farklı yaşayacak olanları ayrı tiplerde tutmak ise sökmeyi ve kurmayı bilerek kabul etmektir.
Özet
- Bu dersin akıbeti: sökülen. Sınırlayıcı ölçümde yer tutucu
/Card/Text, görsel gelince tipi değiştiği için özelliklerine bakılmadan sökülen çıktı; yerine/Card/Imagekurulan oldu. - Bu derste ele alınan çekirdek bileşenler görünüm, metin, görsel ve giriş alanıdır; hepsi ağaçta tipli bir düğümdür ve yerel tarafta tipine uyan bir görünüme karşılık gelir.
- Aynı tipte özellik değişimi düğümü güncellenen yapar: ad değişince
/Card/Text[ad]güncellenen oldu ve komut bir kaldı. - Aynı veri değişikliği vardığı düğüme göre farklı akıbet verir: ad yer tutucuyla tek, görselle iki düğümü güncelledi.
- Tip değişimi iki komut doğurdu (bir sökülen, bir kurulan); görselin yalnız kaynağı değişince tek komut gitti ve düğüm güncellenen oldu.
Sonraki Adım
Bu derste özellikler metin ve kaynak gibi düz değerlerdi. Sıradaki ders, Biçem Sistemi, düğümün görünüşünü belirleyen biçemi ele alır: biçem de ağaca bir özellik olarak girer ve içeriği aynı kalan yeni bir biçem nesnesinin ağaçta ne yaptığını ölçer.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.