İçeriğe geç
academia.sh

Ders 03 / 17

Durum Kaldırma

Durum kaldırma, durumu onu okuyan ve yazan bileşen işlevlerinin ortak atasına taşır; değer aşağı iner, olay yukarı çıkar. Ders sahibe giden olayın yönünü ve sahibin yüksekliğinin hareket sayısını nasıl değiştirdiğini ölçer.

Yayın Güncelleme

Bu derste

  • Durumsuz bir bileşen işlevinin değeri parametreyle, değişiklik isteğini geri çağrıyla aldığını açıklar
  • Okuyucuların ortak atasını durumun sahibi olarak bulur
  • Sahibe giden olayın yukarı, sahibinden inen değerin aşağı gittiğini ölçer
  • Durum gereğinden yükseğe kaldırıldığında hareket sayısının arttığını, yönün değişmediğini gösterir
İçindekiler

Önceki derste Screen bir adedi hatırladı ve iki çocuğu değeri okudu. Değer aşağı indi, ama okuyucu o değeri değiştirmek istediğinde yapabileceği bir şey yoktu: durum onun değil, atasına aitti. Bu derste okuyucunun isteği sahibine döner. Durumu okuyan ve yazan bileşen işlevlerinin ortak atasına taşımaya durum kaldırma denir. Dersin sorusu önceki derslerle aynıdır: bu veri arayüz ağacında hangi yöne gidiyor?

// 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 }}")
    }
}

Değer Aşağı, Olay Yukarı

Platform belgesi durumu, onu okuyan ve yazan bileşen işlevlerinin en alt ortak atasına kaldırmayı önerir. Durumun sahibi tüketicilere değiştirilemez durumu ve onu değiştirecek olayları sunar. Bileşen işlevi bunu iki parametreyle alır: gösterilecek value ve değişiklik istendiğinde çağrılacak onValueChange. Durumu kendi tutmayan işleve durumsuz denir; durumu tutup bir durumsuz işleve veren işleve durumlu.

// Platform kodu: QuantityScreen
import androidx.compose.runtime.*

@Composable
fun QuantityField(value: Int, onValueChange: (Int) -> Unit) {
    Button(onClick = { onValueChange(value + 1) }) {
        Text("Adet: $value")
    }
}

@Composable
fun QuantityScreen() {
    var quantity by remember { mutableStateOf(1) }
    QuantityField(value = quantity, onValueChange = { quantity = it })
}

QuantityField durumu tutmaz. Değeri parametreyle alır, dokunuşu geri çağrıyla bildirir; yeni değeri yazmak QuantityScreen’in işidir. Değer ebeveynden çocuğa, olay çocuktan ebeveynine gider. Buna tek yönlü veri akışı denir: her hareket tek yönlüdür, ama iki hareket zıt yönlere gider. Bu parça platform kodudur ve burada çıktı, düzen ya da süre iddia etmiyoruz. Model, platformun bu kuralını kurar; model platformun kendisi değildir.

Bu ayrım M12/K03’ten farklıdır. O kursun “bu sonucu kim değiştirebiliyor” sorusu sonucun sahibini sınıflar. Bu derste sahibi bir model işlevi bulur, ama ölçü sahip değildir: ölçü, sahibe giden ve sahibinden çıkan hareketin yönüdür.

Ortak Ata Sahip Olur

Model, okuyucuların en alt ortak atasını bulan bir işlevle başlar. Ad bu derste yenidir; açılış dersindeki hoistTo ile aynı işi yapar.

// Owner.kt — okuyucularin en alt ortak atasi durumun sahibidir
fun commonOwner(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 } }
}

Sepet ekranında adedi CartList değiştirir ve CheckoutButton toplamı göstermek için okur. Bunlar kardeştir. Önceki dersten biliyoruz ki kardeşler arasında doğrudan hareket kurulamaz; ikisinin paylaştığı değer ortak atadan geçer. Bir yardımcı bu bölünmeyi kurar: kaynaktan ortak sahibe bir yukarı hareket, sahibinden hedefe bir aşağı hareket.

// Siblings.kt — kardesler arasi istegi ortak ata uzerinden iki harekete boler
fun viaOwner(label: String, from: String, to: String, tree: Tree): List<Move> {
    val owner = commonOwner(listOf(from, to), tree)
    return listOf(
        Move("$label-yukari", from, owner, tree),
        Move("$label-asagi", owner, to, tree),
    )
}
// Main.kt — kardesten kardese hareket iki harekettir
fun main() {
    val tree = Tree(mapOf(
        "Screen" to "App",
        "Header" to "Screen",
        "CartList" to "Screen",
        "CartRow" to "CartList",
        "CheckoutButton" to "Screen",
    ))
    println("durum sahibi: ${commonOwner(listOf("CartList", "CheckoutButton"), tree)}")
    val moves = viaOwner("adet", "CartList", "CheckoutButton", tree)
    moves.forEach { it.describe() }
    countDirections(moves)
}
durum sahibi: Screen
adet-yukari: kimden=CartList | kime=Screen -> yukari
adet-asagi: kimden=Screen | kime=CheckoutButton -> asagi
asagi: 1
yukari: 1
derin: 0
dis: 0

Sahip Screen çıktı. CartList’ten CheckoutButton’a giden istek önce sahibe çıktı: yukarı. Sahipten düğmeye indi: aşağı. Bu dersin yönü olan yukarı, kardeşler arası her paylaşımın ilk yarısıdır.

BI5. Model sahibi yalnız okuyucuların ve yazanın düğüm adlarından bulur. Platformda sahibin yazma ve okuma mantığının arayüz mantığı mı iş mantığı mı olduğuna göre ağacın dışında, örneğin bir ViewModel’de de durabilmesi modelde yoktur.

Tam Akış

Şimdi tüm akışı kuralım. Sahip commonOwner ile seçilir. Her okuyucuya sahibinden değer iner, yazan düğümden sahibe olay çıkar. Değer ve olay aradaki her düzeyden parametre ve geri çağrıyla geçer; bir düzey atlanmaz. Bunu yapan yardımcı bir aşağı yolu çıkarır.

// Flow.kt — sahipten okuyuculara inen degerler, yazandan sahibe cikan olay
fun downPath(owner: String, node: String, tree: Tree): List<String> =
    ((listOf(node) + tree.ancestors(node)).takeWhile { it != owner } + owner).reversed()

fun flowMoves(owner: String, readers: List<String>, writer: String, tree: Tree): List<Move> {
    val values = readers
        .flatMap { reader -> downPath(owner, reader, tree).zipWithNext() }
        .distinct()
        .map { (from, to) -> Move("deger-${to.lowercase()}", from, to, tree) }
    val events = downPath(owner, writer, tree).reversed().zipWithNext()
        .map { (from, to) -> Move("olay-${from.lowercase()}", from, to, tree) }
    return values + events
}

fun liftTo(node: String, levels: Int, tree: Tree): String = tree.ancestors(node)[levels - 1]

flowMoves aynı hareketi iki okuyucu için iki kez saymaz; distinct ortak yol parçasını tek hareket yapar. liftTo bir düğümün birkaç düzey üstünü verir ve bir sonraki bölümde kullanılacak.

// Main.kt — deger asagi, olay yukari
fun main() {
    val tree = Tree(mapOf(
        "Screen" to "App",
        "Header" to "Screen",
        "CartList" to "Screen",
        "CartRow" to "CartList",
        "CheckoutButton" to "Screen",
    ))
    val readers = listOf("CartList", "CheckoutButton")
    val owner = commonOwner(readers, tree)
    println("durum sahibi: $owner")
    val moves = flowMoves(owner, readers, "CartList", tree)
    moves.forEach { it.describe() }
    countDirections(moves)
}
durum sahibi: Screen
deger-cartlist: kimden=Screen | kime=CartList -> asagi
deger-checkoutbutton: kimden=Screen | kime=CheckoutButton -> asagi
olay-cartlist: kimden=CartList | kime=Screen -> yukari
asagi: 2
yukari: 1
derin: 0
dis: 0

İki değer sahibinden çocuklarına iner: iki aşağı. Adedi değiştiren dokunuş CartList’ten sahibe çıkar: bir yukarı. Sayı olarak aşağı önde, ama durum kaldırmanın yeni getirdiği hareket olaydır: durum yerelken olay yoktu, sahibe taşınınca yukarı gidecek bir istek doğdu. Dersin yönü bu yüzden yukarıdır.

Durum yalnız bir bileşen işlevine aitse kaldırılacak bir şey yoktur. Okuyucu da yazan da CartList ise ortak ata CartList’in kendisidir ve hareket kurulmaz.

// Main.kt — tek okuyucu: sahip okuyucunun kendisi
fun main() {
    val tree = Tree(mapOf(
        "Screen" to "App",
        "CartList" to "Screen",
        "CheckoutButton" to "Screen",
    ))
    val owner = commonOwner(listOf("CartList"), tree)
    println("durum sahibi: $owner")
    countDirections(flowMoves(owner, listOf("CartList"), "CartList", tree))
}
durum sahibi: CartList
asagi: 0
yukari: 0
derin: 0
dis: 0

Kaldırma, yalnız paylaşım olduğunda gerekir. Yerel durum önceki dersteki gibi hareket üretmez.

Sınırlayıcı Ölçüm

Durumu gerekenden yükseğe, App’e kaldırırsak ne değişir? Olay hâlâ sahibe gider; yönü yukarıdır. Ama sahip uzaklaştığı için değer ve olay aradaki her düzeyden geçmek zorunda kalır.

// Main.kt — ayni durum bir ust duzeye kaldirilinca hareket sayisi artar
fun main() {
    val tree = Tree(mapOf(
        "Screen" to "App",
        "Header" to "Screen",
        "CartList" to "Screen",
        "CartRow" to "CartList",
        "CheckoutButton" to "Screen",
    ))
    val readers = listOf("CartList", "CheckoutButton")
    val needed = commonOwner(readers, tree)
    for (owner in listOf(needed, liftTo(needed, 1, tree))) {
        println("durum sahibi: $owner")
        val moves = flowMoves(owner, readers, "CartList", tree)
        countDirections(moves)
        println("toplam: ${moves.size}")
    }
}
durum sahibi: Screen
asagi: 2
yukari: 1
derin: 0
dis: 0
toplam: 3
durum sahibi: App
asagi: 3
yukari: 2
derin: 0
dis: 0
toplam: 5

Sahip App olunca değer Screen’e, oradan iki okuyucuya iner: üç aşağı hareket. Olay CartList’ten Screen’e, oradan App’e çıkar: iki yukarı hareket. Toplam üçten beşe çıktı. Dört kategori adı değişmedi; derin ve dis yine sıfır. Screen yalnız iletici oldu: değeri kullanmıyor, ama imzasında taşıyor. Platform belgesinin öğüdü de budur: durumu en alt ortak atasına kaldır ve gerekmeyen bileşen işlevlerine geçirme.

Kardeşler arası hareketin ortak ata üzerinden iki harekete bölünmesi ve sahibin yüksekliğinin hareket sayısını değiştirmesi aynı kurala bağlıdır: kural yalnız iki ucun ağaçtaki konumuna bakar. Hangi yolu seçtiğimizi ders söyler, kural değil.

BI6. Model, iletici düğümlerin değeri ve olayı olduğu gibi aktardığını varsayar. Platformda aradaki bileşen işlevinin değeri dönüştürmesi ya da olayı kendisinin işlemesi modelde yoktur.

Özet

  • Bu dersin yönü: yukarı. Durum kaldırılınca okuyucudan sahibine yeni bir hareket doğar: değişiklik isteği.
  • Sahip, okuyucuların en alt ortak atasıdır: CartList ve CheckoutButton için Screen.
  • Kardeşler arası istek iki harekete bölünür: yukarı 1, aşağı 1.
  • Tam akışta iki değer aşağı iner, bir olay yukarı çıkar: aşağı 2, yukarı 1.
  • Durum App‘e kaldırılınca aşağı 3, yukarı 2 olur; toplam hareket 3’ten 5’e çıkar, kategoriler aynı kalır.

Sonraki Adım

Bu derste olay sahibine döndü ve sahip değeri yeniden indirdi. Değer değişince hangi bileşen işlevlerinin yeniden çalışacağı ayrı bir sorudur. Sıradaki ders, Yeniden Oluşturma, değişikliğin sahibinden hangi düğümlere ulaştığını ve değerin okunmasının ertelenmesinin yönü nasıl değiştirdiğini ölçer.

İ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