State Hoisting: بالا بردن وضعیت و جریان داده یکطرفه در Compose

نویسنده: IT Sectr منتشر شده: 2026-06-28 زمان مطالعه: 8 دقیقه

State Hoisting — الگویی در Jetpack Compose است که در آن وضعیت از تابع فرعی Composable به تابع والد منتقل می‌شود، و تابع فرعی داده‌ها را از طریق پارامترها دریافت کرده و تغییرات را از طریق callback‌ها اعلام می‌کند. این پیاده‌سازی اصل جریان داده یکطرفه (UDF) است که در آن وضعیت به بالا و رویدادها به پایین جریان دارند. به گزارش Google Android Developers, 2026، State Hoisting کامپوننت‌ها را قابل استفاده مجدد، قابل آزمایش و قابل پیش‌بینی می‌سازد.

نکات کلیدی

  • State Hoisting وضعیت را از کامپوننت فرعی به والد منتقل می‌کند
  • UDF (Unidirectional Data Flow) — وضعیت به پایین، رویدادها به بالا جریان دارند
  • پارامترهای کامپوننت فرعی: مقدار (T) + لامبدا (T) -> Unit
  • استفاده مجدد — وضعیت بالا برده شده امکان استفاده از همان تابع را با منابع مختلف فراهم می‌کند
  • آزمایش — State Hoisting آزمایش‌های واحد را با جدا کردن منطق از UI ساده‌تر می‌کند

State Hoisting در Jetpack Compose چیست

State Hoisting (بالا بردن وضعیت) — الگویی است که در آن تابع Composable وضعیتی ندارد، بلکه آن را از خارج دریافت می‌کند. به جای استفاده از var داخل تابع، از دو پارامتر استفاده می‌شود: مقدار برای نمایش و لامبدا-کالبک برای مدیریت تغییرات. از نظر فنی، این به معنای این است که کامپوننت فرعی بدون وضعیت (stateless) و والد با وضعیت (stateful) می‌شود.

مثال: کامپوننت TextField از Material3 متن وارد شده را داخل خود ذخیره نمی‌کند. آن value: String و onValueChange: (String) -> Unit را می‌پذیرد. والدی که TextField را فراخوان می‌کند، var value by remember { mutableStateOf("") } را اعلام کرده و value و onValueChange را ارسال می‌کند. این یک State Hoisting کلاسیک است: TextField — کامپوننت ساده (فقط نمایش می‌دهد و از ورود خبر می‌دهد)، والد — هوشمند (وضعیت را دارد).

Stateless در مقابل Stateful: کامپوننت Stateless آزمودن ساده‌تر است — آن به وضعیت داخلی وابسته نیست، رفتارش تماماً توسط پارامترهای ورودی تعیین می‌شود. کامپوننت Stateful برای نمونه‌سازی سریع مناسب است، اما استفاده مجدد از آن سخت‌تر است — آن به یک منبع داده محدود شده است. State Hoisting انتخاب را می‌دهد: هر کامپوننتی را می‌توان با انتقال وضعیت به بالا، stateless کرد.

جریان داده یکطرفه (UDF) و State Hoisting

UDF (Unidirectional Data Flow) — اصلی معماری است که در آن داده‌ها در یک جهت جریان دارند: از منبع حقیقت (ViewModel یا والد Composable) به UI، و رویدادها در جهت مخالف. State Hoisting پیاده‌سازی UDF در سطح کامپوننت‌های جداگانه است. به جای اینکه هر کامپوننت خودش تصمیم بگیرد که کی و چگونه وضعیتش را تغییر دهد، آن در باره رویداد به والد اطلاع می‌دهد، و والد تصمیم می‌گیرد چگونه وضعیت را تغییر دهد.

مزایای UDF: قابلیت پیش‌بینی — وضعیت تنها در یک جا تغییر می‌کند، که race conditions را از بین می‌برد؛ قابلیت ردیابی — از روی پیش‌های صدا زنی می‌توان زنجیره تغییرات را بازسازی کرد؛ آزمایش — منطق هاستفول را می‌توان در یک کلاس جداگانه قرار داده و بدون UI آزمود. در پروژه‌های بزرگ، UDF همراه با State Hoisting یک استاندارد ده فاکتو است.

تک منبع حقیقت (Single Source of Truth) — اصل دیگری که همراه UDF می‌آید. هر بخش وضعیت دقیقاً یک منبع دارد. اگر دو کامپوننت از یک وضعیت استفاده می‌کنند، منبع باید مشترک باشد (در سطح ViewModel یا والد مشترک). State Hoisting تضمین می‌کند که منبع در بالای سلسله مراتب قرار داشته و از تکرار وضعیت جلوگیری شود.

جهتچه انتقال داده می‌شودچگونه پیاده شده
به پایین (والد → فرعی)مقدار برای نمایشپارامتر value: T
به بالا (فرعی → والد)رویداد تغییرپارامتر onValueChange: (T) -> Unit

قواعد State Hoisting: کی و چگونه وضعیت را بالا ببریم

قاعده اصلی: وضعیت باید به مینیمال سطح ممکن بالا برده شود که برای همه کامپوننت‌هایی که به آن نیاز دارند کافی باشد. اگر وضعیت فقط داخل یک کامپوننت استفاده می‌شود — آن را محلی نگه دارید. اگر دو کامپوننت مجاور به یک وضعیت نیاز دارند — به والد مشترک بالا ببرید. اگر وضعیت در تمام صفحه مورد نیاز است — به ViewModel بالا ببرید.

قاعده بالا بردن مینیمال از پیچیدگی بی‌لورض جلوگیری می‌کند. بالا بردن وضعیت یک فیلد متنی به ViewModel اگر تنها داخل یک صفحه استفاده شود و در بازسازی Activity ذخیره نمی‌شود، بی‌معنی است. برای وضعیت UI که باید چرخش صفحه را تحمل کند اما برای منطق کسب و کار لازم نیست، از rememberSaveable در سطح والد صفحه به جای ViewModel استفاده کنید.

کی به ViewModel بالا ببریم: اگر وضعیت باید در بازسازی Activity ذخیره شود، اگر چند صفحه به آن نیاز دارند، اگر تغییر وضعیت منطق کسب و کار (درخواست‌های شبکه، پایگاه داده) را تحریک می‌کند. State Hoisting در سطح ViewModel یک الگوی نمونه در معماری MVVM است، جایی که لایه UI stateless و ViewModel stateful است.

kotlin
    // ❌ بد: کامپوننت وضعیت خود را دارد
@Composable
fun BadTextField(label: String) {
    var text by remember { mutableStateOf("") }
    TextField(value = text, onValueChange = { text = it }, label = { Text(label) })
}

    // ✅ خوب: State Hoisting — وضعیت در والد
@Composable
fun GoodTextField(value: String, onValueChange: (String) -> Unit, label: String) {
    TextField(value = value, onValueChange = onValueChange, label = { Text(label) })
}

    // استفاده: والد وضعیت را دارد
@Composable
fun Form() {
    var name by rememberSaveable { mutableStateOf("") }
    GoodTextField(value = name, onValueChange = { name = it }, label = "Name")
}

مصالیهای State Hoisting در کامپوننت‌های واقعی

صفحه ورود را در نظر بگیرید که دو فیلد (email، رمز عبور) و یک دکمه دارد. هر سه کامپوننت وضعیت را از طریق State Hoisting دریافت می‌کنند: email و رمز عبور توسط والد مدیریت می‌شوند، دکمه وضعیت enabled را به عنوان یک مقدار دریافت می‌کند.

kotlin
    // State Hoisting در سطح صفحه
@Composable
fun LoginScreen(viewModel: LoginViewModel) {
    val uiState by viewModel.uiState.collectAsState()

    Column(modifier = Modifier.padding(16.dp)) {
        // فیلد Email — State Hoisting از طریق لامبدا
        EmailField(
            email = uiState.email,
            onEmailChange = { viewModel.onEmailChanged(it) }
        )

        // فیلد رمز عبور — همینطور
        PasswordField(
            password = uiState.password,
            onPasswordChange = { viewModel.onPasswordChanged(it) }
        )

        // دکمه — فقط enabled را دریافت می‌کند (فقط خواندنی)
        LoginButton(enabled = uiState.isFormValid, onClick = viewModel::login)
    }
}

// کامپوننت Stateless: email + callback دریافت می‌کند
@Composable
fun EmailField(email: String, onEmailChange: (String) -> Unit) {
    OutlinedTextField(
        value = email,
        onValueChange = onEmailChange,
        label = { Text("Email") },
        singleLine = true
    )
}

// کامپوننت دکمه Stateless
@Composable
fun LoginButton(enabled: Boolean, onClick: () -> Unit) {
    Button(onClick = onClick, enabled = enabled) {
        Text("ورود")
    }
}

EmailField و PasswordField — کاملاً stateless. می‌توان آنها را در هر صفحه‌ای با اتصال به هر منبع داده‌ای مجدداً استفاده کرد. LoginButton enabled را به صورت تنها خواندنی دریافت می‌کند — این شکل دیگری از State Hoisting است که در آن وضعیت بالا برده نمی‌شود (دکمه نمی‌تواند خودش را فعال کند)، بلکه آماده ارسال می‌شود. این رویکرد با کمترین وابستگی کامپوننت‌ها، حداکثر انعطاف‌پذیری را فراهم می‌کند.

State Hoisting در مقابل وضعیت محلی: معیارهای انتخاب

هر وضعیتی نیاز به بالا بردن ندارد. وضعیت محلی (State داخل Composable) موجه است زمانی که: داده‌ها فقط داخل یک کامپوننت مورد نیاز هستند، بر عناصر مجاور تأثیر نمی‌گذارند، نیازی به تحمل بازسازی بخش مشخص نیست. مثالاً، وضعیت انیمیشن، فوکوس فیلد ورودی، موقعیت افقی پیمایش — مقدر است محلی نگه داشته شوند.

وقتی State Hoisting ضروری است: وضعیت توسط چند کامپوننت فرعی استفاده می‌شود؛ تغییر در یک فرعی باید در دیگری منعکس شود؛ منطق تغییر وضعیت باید جداگانه از UI آزموده شود؛ وضعیت باید در بازسازی Activity ذخیره شود. در این موارد، وضعیت محلی باعث تکرار و ناهماهنگی داده‌ها می‌شود.

رویکرد ترکیبی: حداقل وضعیت را محلی نگه داشته و باقی را بالا ببرید. قاعده Compose: «وضعیت را تا آنجا که لازم است بالا ببرید و تا آنجا که ممکن است پایین نگه دارید». در عمل، این به معنای شروع با remember محلی است، و تنها وقتی نیاز به دسترسی از کامپوننت دیگر پیدا شود — سطح را بالا ببرید. State Hoisting را احتیاطی انجام ندهید — این کد را بدون لزوم پیچیده می‌کند.

سوالات متداول

تفاوت State Hoisting با ViewModel چیست؟

State Hoisting — الگویی در سطح کامپوننت‌های UI است. ViewModel — لایه معماری برای منطق کسب و کار است. State Hoisting می‌تواند وضعیت را به سطح والد Composable، سطح صفحه یا ViewModel بالا ببرد. ViewModel — بالاترین نقطه بالا بردن برای وضعیتی است که باید از بازسازی Activity جان سالم به در آید.

چگونه کامپوننت را با State Hoisting آزمایش کنیم؟

کامپوننت Stateless با انتقال ساده مقادیر آزموده می‌شود. Composable را با پارامترهای مورد نیاز فراخوان کرده و نمایش را از طریق ComposeTestRule بررسی کنید. تغییر وضعیت در سطح والد یا ViewModel — جداگانه از UI آزموده می‌شود. این تست‌ها را بسیار ساده‌تر می‌کند: نیازی به شبیه‌سازی بازسازی داخل کامپوننت نیست.

آیا می‌توان State را فقط برای خواندن بالا برد؟

بله، این یک روش رایج است. اگر کامپوننت فقط باید داده‌ها را بدون امکان تغییر نمایش دهد — State<T> (فقط خواندنی) ارسال کنید. کامپوننت در تغییرات مشترک خواهد شد، اما نمی‌تواند آنها را ایجاد کند. این کپسولاسیون را تقویت کرده و از داده‌ها در برابر تغییرات نامطلوب محافظت می‌کند.

اگر نیاز به بالا بردن وضعیت تا 3+ سطح تودر داشته باشیم چه کنیم؟

برای ارسال عمیق از CompositionLocal یا ارسال از طریق پارامترهای والد Composable استفاده کنید. اگر وضعیت در تمام صفحه مورد نیاز است — آن را به ViewModel منتقل کرده و از collectAsState() استفاده کنید. ارسال از طریق 5+ سطح نشانه معماری نادرست است؛ سلسله مراتب کامپوننت‌ها را مرور کنید.

آیا State Hoisting بر عملکرد تأثیر می‌گذارد؟

State Hoisting ممکن است تعداد بازسازی‌ها را اندکی افزایش دهد، زیرا تغییر در والد می‌تواند همه فرعی‌ها را بازسازی کند. برای فیلتر تغییرات از derivedStateOf و برای به‌روزرسانی های نقطه‌ای در LazyColumn از keys استفاده کنید. در اکثر سناریوها، سررایه State Hoisting در مقایسه با مزایای قابلیت حفظ ناچیز است.

خلاصه

  • State Hoisting وضعیت را به کامپوننت والد منتقل کرده و فرعی را stateless می‌کند
  • UDF جریان داده یکطرفه را تضمین می‌کند: وضعیت به پایین، رویدادها به بالا
  • استفاده مجدد — کامپوننت‌های stateless را می‌توان به هر منبع داده‌ای متصل کرد
  • آزمایش — تست‌های UI فقط نمایش را بررسی می‌کنند، منطق جداگانه آزموده می‌شود
  • بالا بردن مینیمال — دقیقاً به اندازه نیاز بالا ببرید
  • ViewModel — بالاترین نقطه بالا بردن برای وضعیت با منطق کسب و کار
  • توصیه: با remember محلی شروع کرده، فقط در صورت نیاز بالا ببرید

ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد

IT Sectr از سال 2017 برنامه‌های iOS و Android را برای استارتاپ‌ها و کسب‌وکارها ایجاد می‌کند. ما به شما مشاوره می‌دهیم و بهترین راه‌حل را پیشنهاد خواهیم کرد.

بحث درباره پروژه

همچنین بخوانید