State Hoisting: állapot emelés és egyirányú adatáramlás a Compose-ban

Szerző: IT Sectr Megjelenés: 2026-06-28 Olvasási idő: 8 perc

State Hoisting — egy mintázat a Jetpack Compose-ban, ahol az állapot a gyermek Composable függvényből a szülőbe kerül áthelyezésre, és a gyermek paramétereken keresztül kapja meg az adatokat, és callback-eken keresztül értesít a változásokról. Ez az egyirányú adatáramlás (UDF) elvének megvalósítása, ahol az állapot felfelé emelkedik, az események pedig lefelé haladnak. A Google Android Developers, 2026 szerint a State Hoisting újrafelhasználhatóvá, tesztelhetővé és kiszámíthatóvá teszi a komponenseket.

Főbb pontok

  • State Hoisting áthelyezi az állapotot a gyermek komponensből a szülőbe
  • UDF (Unidirectional Data Flow) — állapot lefelé, események felfelé áramlanak
  • Paraméterek gyermek komponenshez: érték (T) + lambda (T) -> Unit
  • Újrafelhasználás — az emelt állapot lehetővé teszi ugyanazon függvény használatát különböző forrásokkal
  • Tesztelés — a State Hoisting egyszerűsíti az egységteszteket a logika elkülönítésével az UI-tól

Mi az a State Hoisting a Jetpack Compose-ban

State Hoisting (állapot emelés) — egy mintázat, ahol a Composable függvény nem birtokolja az állapotot, hanem külről kapja. A függvényen belüli var helyett két paramétert használunk: a megjelenítésre szánt értéket és egy lambda-callback-et a változások kezeléséhez. Technikailag ez azt jelenti, hogy a gyermek komponens statelessé válik (nincs saját állapota), a szülő pedig stateful (birtokolja az állapotot).

Példa: a Material3-ból származó TextField komponens nem tárolja a beírt szöveget magában. Elfogadja a value: String és onValueChange: (String) -> Unit paramétereket. A TextField-et meghívó szülő deklarálja a var value by remember { mutableStateOf("") }-t, és átadja a value-t és onValueChange-t. Ez klasszikus State Hoisting: TextField — egyszerű komponens (csak megjelenít és jelent a bevitelről), a szülő — okos (birtokolja az állapotot).

Stateless vs Stateful: A Stateless komponenst könnyebb tesztelni — nem függ a belső állapottól, viselkedését teljes mértékben a bemeneti paraméterek határozzák meg. A Stateful komponens kényelmes a gyors prototípuskészítéshez, de nehezebb újrafelhasználni — szorosan kötődik egy adatforráshoz. A State Hoisting választást ad: bármely komponens statelessé tehető az állapot felfelé helyezésével.

Egyirányú adatáramlás (UDF) és State Hoisting

UDF (Unidirectional Data Flow) — egy architekturális elv, ahol az adatok egy irányba mozognak: az igazság forrásától (ViewModel vagy szülő Composable) az UI felé, az események pedig az ellenkező irányba. A State Hoisting az UDF megvalósítása az egyes komponensek szintjén. Ahelyett, hogy minden komponens maga döntené el, mikor és hogyan változtassa meg állapotát, jelenti az eseményt a szülőnek, és a szülő dönt az állapot megváltoztatásáról.

Az UDF előnyei: kiszámíthatóság — az állapot csak egy helyen változik, ami kiküszöböli a race condition-okat; nyomon követhetőség — a hívási veremből rekonstruálható a változások lánca; tesztelés — a stateful logika külön osztályba helyezhető és UI nélkül tesztelhető. Nagy projektekben az UDF és State Hoisting kombinációja de facto szabvány.

Egyetlen igazságforrás (Single Source of Truth) — egy másik elv, amely az UDF-hez társul. Az állapot minden részének pontosan egy forrása van. Ha két komponens ugyanazt az állapotot használja, a forrásnak közösnek kell lennie (ViewModel vagy közös szülő szintjén). A State Hoisting garantálja, hogy a forrás magasabban van a hierarchiában, és nem történik állapot duplicate.

IrányMi kerül átadásraHogyan van megvalósítva
Lefelé (szülő → gyermek)Megjelenítendő értékParaméter value: T
Felfelé (gyermek → szülő)Változási eseményParaméter onValueChange: (T) -> Unit

A State Hoisting szabályai: mikor és hogyan emeljük az állapotot

Fő szabály: az állapotot a lehető legalacsonyabb szintre kell emelni, amely elegendő az összes komponens számára, amelyeknek szükségük van rá. Ha az állapotot csak egy komponensen belül használják — hagyja lokális. Ha két szomszédos komponensnek ugyanarra az állapotra van szüksége — emelje a közös szülőbe. Ha az állapot az egész képernyőn szükséges — emelje a ViewModel-be.

A minimális emelés szabálya megakadályozza a szükségtelen bonyolultságot. Nincs értelme egy szöveges mező állapotát a ViewModel-be emelni, ha azt csak egy képernyőn belül használják, és nem marad meg az Activity újralétrehozásakor. Használja a rememberSaveable-t a képernyő szülő szintjén, ne a ViewModel-t, olyan UI állapothoz, amelynek túl kell élnie a képernyő forgatását, de nem szükséges az üzleti logikának.

Mikor emeljünk a ViewModel-be: ha az állapotnak meg kell maradnia az Activity újralétrehozásakor, ha több képernyőnek van szüksége rá, ha az állapotváltozás üzleti logikát indít el (hálózati kérések, adatbázis). A ViewModel szintű State Hoisting tipikus mintázat az MVVM architektúrában, ahol az UI réteg stateless, a ViewModel pedig stateful.

kotlin
    // ❌ Rossz: a komponens saját állapottal rendelkezik
@Composable
fun BadTextField(label: String) {
    var text by remember { mutableStateOf("") }
    TextField(value = text, onValueChange = { text = it }, label = { Text(label) })
}

    // ✅ Jó: State Hoisting — állapot a szülőben
@Composable
fun GoodTextField(value: String, onValueChange: (String) -> Unit, label: String) {
    TextField(value = value, onValueChange = onValueChange, label = { Text(label) })
}

    // Használat: a szülő birtokolja az állapotot
@Composable
fun Form() {
    var name by rememberSaveable { mutableStateOf("") }
    GoodTextField(value = name, onValueChange = { name = it }, label = "Name")
}

Példák State Hoisting-re valós komponensekben

Tekintsük a bejelentkezési képernyőt, amely két mezővel (email, jelszó) és egy gombbal rendelkezik. Mindhárom komponens állapotot kap a State Hoisting-en keresztül: az email és a jelszó kezelését a szülő végzi, a gomb az enabled állapotot értékként kapja.

kotlin
    // State Hoisting képernyő szinten
@Composable
fun LoginScreen(viewModel: LoginViewModel) {
    val uiState by viewModel.uiState.collectAsState()

    Column(modifier = Modifier.padding(16.dp)) {
        // Email mező — State Hoisting lambdán keresztül
        EmailField(
            email = uiState.email,
            onEmailChange = { viewModel.onEmailChanged(it) }
        )

        // Jelszó mező — hasonlóan
        PasswordField(
            password = uiState.password,
            onPasswordChange = { viewModel.onPasswordChanged(it) }
        )

        // Gomb — csak enabled-et kap (read-only)
        LoginButton(enabled = uiState.isFormValid, onClick = viewModel::login)
    }
}

// Stateless komponens: email + callback fogadása
@Composable
fun EmailField(email: String, onEmailChange: (String) -> Unit) {
    OutlinedTextField(
        value = email,
        onValueChange = onEmailChange,
        label = { Text("Email") },
        singleLine = true
    )
}

// Stateless gomb komponens
@Composable
fun LoginButton(enabled: Boolean, onClick: () -> Unit) {
    Button(onClick = onClick, enabled = enabled) {
        Text("Bejelentkezés")
    }
}

EmailField és PasswordField — teljesen stateless. Bármely képernyőn újrafelhasználhatók, bármilyen adatforráshoz csatlakoztatva. A LoginButton az enabled-et read-only-ként kapja — ez a State Hoisting egy másik formája, ahol az állapot nem emelkedik (a gomb nem tudja magát engedélyezni), hanem készen kerül átadásra. Ez a megközelítés maximális rugalmasságot biztosít minimális komponens összefűzéssel.

State Hoisting vs lokális állapot: választási szempontok

Nem minden állapotot kell emelni. A lokális állapot (State a Composable-en belül) indokolt, amikor: az adatok csak egy komponensen belül szükségesek, nem befolyásolják a szomszédos elemeket, nem kell túlélniük egy adott szakasz recompozícióját. Például, animációs állapot, beviteli mező fókusza, aktuális görgetési pozíció — érdemes lokálisan tartani.

Mikor szükséges a State Hoisting: az állapotot több gyermek komponens használja; az egyik gyermekben történő változásnak tükröződnie kell a másikban; tesztelni kell az állapotváltozás logikáját az UI-tól elkülönítve; az állapotnak meg kell maradnia az Activity újralétrehozásakor. Ezekben az esetekben a lokális állapot adatok duplázódását és inkonzisztenciáját okozza.

Hibrid megközelítés: tartsa a minimális állapotot lokálisan, a többit emelje. A Compose szabálya: “emelje az állapotot olyan magasra, amennyire szükséges, és olyan alacsonyra, amennyire lehetséges”. A gyakorlatban ez azt jelenti, hogy kezdje a lokális remember-rel, és csak amikor felmerül az igény egy másik komponensből történő hozzáférésre — emelje a szintet. Ne alkalmazza a State Hoisting-et megelőző jelleggel — ez szükségtelenül bonyolítja a kódot.

Gyakran Ismételt Kérdések

Miben különbözik a State Hoisting a ViewModel-től?

State Hoisting — egy mintázat az UI komponensek szintjén. ViewModel — egy architekturális réteg az üzleti logika számára. A State Hoisting emelheti az állapotot a szülő Composable szintjére, a képernyő szintjére vagy a ViewModel-be. ViewModel — a legmagasabb emelési pont az állapot számára, amelynek túl kell élnie az Activity újralétrehozását.

Hogyan teszteljük a komponenst State Hoisting-gel?

A stateless komponens egyszerű értékátadással tesztelhető. Hívja meg a Composable-t a szükséges paraméterekkel, és ellenőrizze a megjelenítést a ComposeTestRule segítségével. Az állapotváltozás tesztelése a szülő vagy ViewModel szintjén történik — az UI-tól elkülönítve. Ez jelentősen leegyszerűsíti a teszteket: nem kell szimulálni a recompozíciót a komponensen belül.

Lehet-e az Állapotot csak olvasásra emelni?

Igen, ez bevett gyakorlat. Ha egy komponensnek csak meg kell jelenítenie az adatokat anélkül, hogy módosíthassa azokat — adjon át State<T>-t (read-only). A komponens fel fog iratkozni a változásokra, de nem tudja azokat kezdeményezni. Ez erősíti az enkapszulációt és védi az adatokat a nemkívánatos mutációktól.

Mi van, ha az állapotot 3+ szint mélységbe kell emelni?

Mély átadáshoz használja a CompositionLocal-t vagy az átadást a szülő Composable paraméterein keresztül. Ha az állapot az egész képernyőn szükséges — helyezze át a ViewModel-be, és használja a collectAsState()-ot. Az 5+ szinten keresztüli átadás hibás architektúra jele; vizsgálja újra a komponenshierarchiát.

Befolyásolja-e a State Hoisting a teljesítményt?

State Hoisting kis mértékben növelheti a recompozíciók számát, mivel a szülőben történő változás összes gyermeket recomponálhatja. Használja a derivedStateOf-ot a változások szűréséhez és a keys-t a LazyColumn-ben célzott frissítésekhez. A legtöbb forgatókönyvben a State Hoisting többletterhelése elhanyagolható a karbantarthatóság előnyeihez képest.

Összefoglaló

  • State Hoisting áthelyezi az állapotot a szülő komponensbe, a gyermeket statelesssé téve
  • UDF garantálja az egyirányú adatáramlást: állapot lefelé, események felfelé
  • Újrafelhasználás — a stateless komponensek bármely adatforráshoz csatlakoztathatók
  • Tesztelés — UI tesztek csak a megjelenítést ellenőrzik, a logika külön tesztelve
  • Minimális emelés — pontosan annyira emelje, amennyire szükséges
  • ViewModel — a legmagasabb emelési pont üzleti logikával rendelkező állapothoz
  • Ajánlás: kezdje lokális remember-rel, emeljen csak szükség esetén

Kulcsrakész mobilalkalmazást fejlesztünk

Az IT Sectr 2017 óta készít iOS és Android alkalmazásokat induló vállalkozásoknak és vállalkozásoknak. Tanácsot adunk, és a legjobb megoldást javasoljuk.

Projekt megbeszélése

Olvassa el is