Ders 01 / 17
Bileşen İşlevleri
Bir bileşen işlevi veriyi parametre olarak alır ve arayüz ağacına düğüm ekler. Veri bu ağaçta aşağı, yukarı, derine ya da ağacın dışına gider; ders sekiz hareketi tek bir kuralla sınıflar.
Yayın Güncelleme
Bu derste
- Bir bileşen işlevinin veriyi alıp arayüz ağacına düğüm eklediğini açıklar
- Bir verinin ağaçta aşağı, yukarı, derin ya da dış yöne gittiğini kuralla belirler
- Parametreyle her düzeyden geçirilen değerin birden çok aşağı hareket olduğunu ölçer
- Aynı sağlayıcı değerinin hedefin konumuna göre aşağı ya da derin sayıldığını gösterir
İçindekiler
K02’de uygulamanın platformdan ne istediğine baktık ve her isteği tek bir soruyla ölçtük: bu isteği platform nasıl karşılıyor? Bu kurs aynı uygulamanın arayüzüne geçer. Bildirimsel bir arayüz bir işlevin dönüşüdür: veri girer, arayüz ağacı çıkar. O ağaçta veri sürekli hareket eder ve hareketler aynı yöne gitmez. Ekran durumu bir alt düzeydeki listeye iner, satırdaki dokunma durumun sahibine çıkar, tema rengi aradaki düzeyleri atlayarak en alttaki bileşen işlevine ulaşır, bir ödeme isteği ağacı terk edip bir servise gider.
Bu yüzden kursun ölçüsü K02’ninkinden farklıdır. Bir hareketin değeri alıp almadığına bakmayız; nereden nereye gittiğine bakarız. Kursun sorusu şudur: bu veri arayüz ağacında hangi yöne gidiyor? Dört yanıt vardır ve kurs boyunca değişmez: aşağı, yukarı, derin ve dış. Çıktılarda ASCII yazılır: asagi, yukari, derin, dis.
Ölçüyü yapan sınıflandırıcı aşağıdaki yardımcı dosyadadır. Her derste aynı dosya ilk blok olarak yer alır.
// Direction.kt — bir verinin arayuz agacinda hangi yone gittigini siniflandirir
class Tree(private val parentOf: Map<String, String>) {
fun contains(node: String): Boolean = node in parentOf.keys || node in parentOf.values
fun parent(node: String): String? = parentOf[node]
fun ancestors(node: String): List<String> {
val chain = mutableListOf<String>()
var current = parentOf[node]
while (current != null) {
chain.add(current)
current = parentOf[current]
}
return chain
}
}
class Move(val label: String, val from: String, val to: String, tree: Tree) {
val direction: String = when {
!tree.contains(from) || !tree.contains(to) -> "dis"
to in tree.ancestors(from) -> "yukari"
tree.parent(to) == from -> "asagi"
from in tree.ancestors(to) -> "derin"
else -> error("akrabasiz: $from ve $to")
}
fun describe() {
println("$label: kimden=$from | kime=$to -> $direction")
}
}
fun countDirections(moves: List<Move>) {
for (name in listOf("asagi", "yukari", "derin", "dis")) {
println("$name: ${moves.count { it.direction == name }}")
}
}
Tree arayüz ağacını çocuktan ebeveyne giden bir eşlem olarak taşır. Move bir hareketin etiketini, kaynağını ve hedefini taşır; yönü etiket değil direction özelliği verir ve soruyu sırayla sorar. Uçlardan biri ağaçta yoksa hareket dıştır. Hedef kaynağın atasıysa yukarıdır. Hedefin ebeveyni kaynaksa aşağıdır. Kaynak hedefin atasıysa, ama doğrudan ebeveyni değilse, derindir. İki uç akraba değilse error verir. Bu sonuncusu ölçüm değil, hatadır: kardeşler arasında doğrudan hareket kurulamaz, kaynağı ile hedefi aynı olan hareket de kurulamaz.
Veri Girer, Ağaç Çıkar
Bir bileşen işlevi (@Composable) parametrelerini alır ve çağrıldığı yerde arayüz ağacına düğüm ekler. Arayüz, işlevin döndürdüğü bir değer olarak değil, işlevin içinde çağrılan öteki bileşen işlevlerinin oluşturduğu ağaç olarak ortaya çıkar. Bir bileşen işlevi yalnız başka bir bileşen işlevinden çağrılabilir. Her çağrı ebeveyn ile çocuk arasında bir bağ kurar, ağaç da bu bağlardan oluşur.
// Platform kodu: CartScreen
import androidx.compose.runtime.Composable
@Composable
fun CartScreen(items: List<CartItem>, total: String) {
Header(title = "Sepet")
CartList(items = items)
CheckoutButton(total = total)
}
CartScreen üç bileşen işlevi çağırır; Header, CartList ve CheckoutButton onun çocuklarıdır. items ve total parametreleri CartScreen’den çağrılan işlevlere iner. Bu parça platform kodudur ve burada çıktı, düzen ya da süre iddia etmiyoruz. Dersin ölçtüğü şey, bu çağrı yapısından çıkan ebeveyn ve çocuk ilişkisidir; ölçümü düz Kotlin değerleriyle kurulan bir model yapar. Model, platformun bu kuralını kurar; model platformun kendisi değildir.
Sekiz Hareket, Dört Yön
Model bir alışveriş sepeti ekranıdır. App kökü Screen’i taşır; Screen altında Header, CartList ve CheckoutButton durur, CartList’in çocuğu CartRow’dur. Hareketlerin uçları elle seçilmez, iki model işlevinden gelir. hoistTo, durumu okuyan bileşen işlevlerinin en alt ortak atasını bulur; durumun sahibi odur. nearestProvider, bir düğümün atalarında verilen anahtarı sağlayan ilk düğümü arar.
// Main.kt
fun hoistTo(readers: List<String>, tree: Tree): String {
val paths = readers.map { listOf(it) + tree.ancestors(it) }
return paths.first().first { node -> paths.all { node in it } }
}
fun nearestProvider(node: String, key: String, provides: Map<String, Set<String>>, tree: Tree): String? =
tree.ancestors(node).firstOrNull { key in provides.getOrDefault(it, emptySet()) }
fun main() {
val tree = Tree(mapOf(
"Screen" to "App",
"Header" to "Screen",
"CartList" to "Screen",
"CartRow" to "CartList",
"CheckoutButton" to "Screen",
))
val owner = hoistTo(listOf("CartList", "CheckoutButton", "Header"), tree)
val themeSource = nearestProvider("CartRow", "tema", mapOf("App" to setOf("tema")), tree)!!
println("durum sahibi: $owner")
println("tema saglayicisi: $themeSource")
val moves = listOf(
Move("sepet-listesi", owner, "CartList", tree),
Move("toplam-tutar", owner, "CheckoutButton", tree),
Move("baslik", owner, "Header", tree),
Move("satir-sil", "CartRow", owner, tree),
Move("odeme-tikla", "CheckoutButton", owner, tree),
Move("tema-rengi", themeSource, "CartRow", tree),
Move("odeme-servisi", owner, "PaymentApi", tree),
Move("kayit-gunlugu", owner, "Analytics", tree),
)
moves.forEach { it.describe() }
countDirections(moves)
}
durum sahibi: Screen tema saglayicisi: App sepet-listesi: kimden=Screen | kime=CartList -> asagi toplam-tutar: kimden=Screen | kime=CheckoutButton -> asagi baslik: kimden=Screen | kime=Header -> asagi satir-sil: kimden=CartRow | kime=Screen -> yukari odeme-tikla: kimden=CheckoutButton | kime=Screen -> yukari tema-rengi: kimden=App | kime=CartRow -> derin odeme-servisi: kimden=Screen | kime=PaymentApi -> dis kayit-gunlugu: kimden=Screen | kime=Analytics -> dis asagi: 3 yukari: 2 derin: 1 dis: 2
Sekiz hareketin üçü aşağı, ikisi yukarı, biri derin, ikisi dıştır. Durumun sahibi Screen çıktı. Sepet listesi, toplam tutar ve başlık sahibinden doğrudan çocuklarına iner: aşağı. Satırdaki silme ve ödeme düğmesindeki dokunma, atası olan sahibe çıkar: yukarı. Tema rengi App’ten CartRow’a gider; arada Screen ve CartList vardır ve renk onlara uğramaz: derin. PaymentApi ve Analytics ağaçta yoktur; ödeme isteği ile kayıt satırı ağacı terk eder: dış. Bu dağılım kursun bağlayıcı sayısıdır; sonraki dersler yalnız kendi kesitini ölçer ve bu sayıyı değiştirmez.
BI1. Model ağacı yalnız ebeveyn ilişkisi olarak taşır. Gerçek arayüz ağacının nasıl kurulduğu ve bileşen işlevlerinin hangi sırayla çağrıldığı ölçülmedi.
Parametre Zinciri
Bir bileşen işlevi parametresini doğrudan çocuğuna verirse hareket tektir. Banner işlevinin Greeting(name) çağırması, Banner’dan Greeting’e giden bir harekettir. Hareketin uçları dersin model işlevinden gelir: parameterChain, verinin sahibinden okuyucusuna kadar olan düğümleri ağacın atalarından sıralar.
// Chain.kt — verinin sahibinden okuyucusuna parametre zinciri
fun parameterChain(owner: String, reader: String, tree: Tree): List<String> =
((listOf(reader) + tree.ancestors(reader)).takeWhile { it != owner } + owner).reversed()
// Main.kt — Greeting(name) cagrisi: ebeveynden cocuga tek hareket
fun main() {
val tree = Tree(mapOf(
"Screen" to "App",
"Header" to "Screen",
"Banner" to "Header",
"Greeting" to "Banner",
))
val path = parameterChain("Banner", "Greeting", tree)
val moves = path.zipWithNext().map { (from, to) -> Move("selam-adi", from, to, tree) }
println("zincir: ${path.joinToString(" > ")}")
moves.forEach { it.describe() }
countDirections(moves)
}
zincir: Banner > Greeting selam-adi: kimden=Banner | kime=Greeting -> asagi asagi: 1 yukari: 0 derin: 0 dis: 0
Ad doğrudan ebeveynden çocuğa gitti: tek aşağı hareket. Şimdi aynı ad ekranın en üstünde, Screen’de bilinsin ve Greeting’e ulaşması gereksin. Header ve Banner adı kendileri kullanmaz, yalnız bir alt düzeye iletir.
// Main.kt — ayni ad uc duzey iner: uc ayri asagi hareket
fun main() {
val tree = Tree(mapOf(
"Screen" to "App",
"Header" to "Screen",
"Banner" to "Header",
"Greeting" to "Banner",
))
val path = parameterChain("Screen", "Greeting", tree)
val moves = path.zipWithNext().map { (from, to) -> Move("ad-${to.lowercase()}", from, to, tree) }
println("zincir: ${path.joinToString(" > ")}")
moves.forEach { it.describe() }
countDirections(moves)
}
zincir: Screen > Header > Banner > Greeting ad-header: kimden=Screen | kime=Header -> asagi ad-banner: kimden=Header | kime=Banner -> asagi ad-greeting: kimden=Banner | kime=Greeting -> asagi asagi: 3 yukari: 0 derin: 0 dis: 0
Veri her düzeyden parametreyle geçirildiğinde üç ayrı aşağı hareket olur; hiçbiri derin değildir. Parametre zinciri, atlanan düzey bırakmaz. Ortadaki Header ve Banner adı kullanmasa da imzalarında taşımak zorundadır.
Kardeşler Arasında Hareket Yok
Sepet ekranında Header ile CartList kardeştir. Başlıktaki bir değerin liste tarafından okunması gerektiğini düşünelim. Bu iki uç akraba değildir: hiçbiri ötekinin atası ya da çocuğu olmadığı için Move("x", "Header", "CartList", tree) satırı direction içindeki son dala düşer ve error verir. Bu bir sınıflandırma değil, kurulamayan bir hareket demektir. Bileşen işlevleri arasında da gerçek bir yol yoktur: iki kardeş ortak ebeveyni üzerinden konuşur. Değer önce ortak ataya çıkar, yani yukarı gider; oradan öteki kardeşe iner, yani aşağı gider. Kardeşler arası tek bir hareket iki harekete bölünür ve bu bölünme, bir sonraki derslerde durumun neden ortak atada durduğunu açıklayacak.
İki ucu da elle yazılan bir satır, yani Move("x", "Screen", "Header", tree) gibi, kuralı göstermek için kullanılabilir ama ölçüm sayılmaz. Bu derste ve kursun geri kalanında ölçüm sayılan her hareketin en az bir ucu dersin bir model işlevinden gelir; kuralı göstermek için yazılan satırlar düzyazıda böyle anılır. Yön ise hiçbir zaman elle yazılmaz.
Sınırlayıcı Ölçüm
Sınıflandırıcı geometriyi görür, kanalı görmez. Verinin parametreyle mi, geri çağrıyla mı, örtük bir sağlayıcıyla mı taşındığını ayırmaz; yalnız iki ucun ağaçtaki konumuna bakar. Bunu görmek için tek bir sağlayıcıyı, tek bir kanalı ve iki okuyucuyu alalım. App temayı sağlar. Okuyuculardan biri doğrudan çocuğu Screen, öteki iki düzey aşağıdaki CartRow’dur.
// Main.kt — ayni saglayici, ayni kanal, iki yon
fun nearestProvider(node: String, key: String, provides: Map<String, Set<String>>, tree: Tree): String? =
tree.ancestors(node).firstOrNull { key in provides.getOrDefault(it, emptySet()) }
fun main() {
val tree = Tree(mapOf(
"Screen" to "App",
"Header" to "Screen",
"CartList" to "Screen",
"CartRow" to "CartList",
))
val provides = mapOf("App" to setOf("tema"))
val moves = listOf("Screen", "CartRow").map { reader ->
Move("tema-${reader.lowercase()}", nearestProvider(reader, "tema", provides, tree)!!, reader, tree)
}
moves.forEach { it.describe() }
countDirections(moves)
}
tema-screen: kimden=App | kime=Screen -> asagi tema-cartrow: kimden=App | kime=CartRow -> derin asagi: 1 yukari: 0 derin: 1 dis: 0
Sağlayıcı aynı, kanal aynı, değer aynı; sonuç iki yön. Screen sağlayıcının doğrudan çocuğudur ve değer ona aşağı gider. CartRow iki düzey aşağıdadır ve değer aradakilere uğramadan ona derin gider. Bir sağlayıcı değerinin her zaman derin olduğunu söylemek yanlıştır: yönü sağlayıcının hedefe olan uzaklığı belirler. Aynı değeri Screen’den CartList’e, oradan CartRow’a parametreyle geçirseydik iki aşağı hareket olurdu. Kanalı ayırmak kuralın işi değildir; ders hangi kanalı kurduğunu her seferinde söyler.
BI2. Model sağlayıcıyı bir anahtar kümesi olarak taşır ve yalnız en yakın sağlayıcıyı arar. Aradaki bir düğümün aynı anahtarı yeniden sağlaması, bu derste ölçülen sekiz hareketin hiçbirinde yoktur.
Özet
- Bu dersin yönü: aşağı. Bir bileşen işlevi veriyi parametreyle alır ve ebeveynden doğrudan çocuğa giden her hareket aşağıdır.
- Kursun çekirdek ölçümünde sekiz hareketin dağılımı aşağı 3, yukarı 2, derin 1, dış 2’dir; sonraki dersler bu sayıyı değiştirmez.
- Aynı ad üç düzey boyunca parametreyle geçirildiğinde üç ayrı aşağı hareket olur: aşağı 3, derin 0.
- Aynı sağlayıcı değeri doğrudan çocuğa aşağı, iki düzey aşağıdaki torununa derin gider: aşağı 1, derin 1.
- Kural geometriyi görür, kanalı görmez; ders hangi kanalı kurduğunu söyler.
Sonraki Adım
Bu derste veri sahibinden okuyucusuna parametreyle indi. Sıradaki ders, Durum ve Hatırlama, durumun bir bileşen işlevi çağrıları arasında nasıl hatırlandığını ve durumun sahibinden okuyucusuna giden değerin hangi yöne gittiğini ölçer.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.