State Hoisting — الگویی در Jetpack Compose است که در آن وضعیت از تابع فرعی Composable به تابع والد منتقل میشود، و تابع فرعی دادهها را از طریق پارامترها دریافت کرده و تغییرات را از طریق callbackها اعلام میکند. این پیادهسازی اصل جریان داده یکطرفه (UDF) است که در آن وضعیت به بالا و رویدادها به پایین جریان دارند. به گزارش Google Android Developers, 2026، State Hoisting کامپوننتها را قابل استفاده مجدد، قابل آزمایش و قابل پیشبینی میسازد.
نکات کلیدی
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 (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 |
قاعده اصلی: وضعیت باید به مینیمال سطح ممکن بالا برده شود که برای همه کامپوننتهایی که به آن نیاز دارند کافی باشد. اگر وضعیت فقط داخل یک کامپوننت استفاده میشود — آن را محلی نگه دارید. اگر دو کامپوننت مجاور به یک وضعیت نیاز دارند — به والد مشترک بالا ببرید. اگر وضعیت در تمام صفحه مورد نیاز است — به ViewModel بالا ببرید.
قاعده بالا بردن مینیمال از پیچیدگی بیلورض جلوگیری میکند. بالا بردن وضعیت یک فیلد متنی به ViewModel اگر تنها داخل یک صفحه استفاده شود و در بازسازی Activity ذخیره نمیشود، بیمعنی است. برای وضعیت UI که باید چرخش صفحه را تحمل کند اما برای منطق کسب و کار لازم نیست، از rememberSaveable در سطح والد صفحه به جای ViewModel استفاده کنید.
کی به ViewModel بالا ببریم: اگر وضعیت باید در بازسازی Activity ذخیره شود، اگر چند صفحه به آن نیاز دارند، اگر تغییر وضعیت منطق کسب و کار (درخواستهای شبکه، پایگاه داده) را تحریک میکند. State Hoisting در سطح ViewModel یک الگوی نمونه در معماری MVVM است، جایی که لایه UI stateless و ViewModel stateful است.
// ❌ بد: کامپوننت وضعیت خود را دارد
@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")
}
صفحه ورود را در نظر بگیرید که دو فیلد (email، رمز عبور) و یک دکمه دارد. هر سه کامپوننت وضعیت را از طریق State Hoisting دریافت میکنند: email و رمز عبور توسط والد مدیریت میشوند، دکمه وضعیت enabled را به عنوان یک مقدار دریافت میکند.
// 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 داخل Composable) موجه است زمانی که: دادهها فقط داخل یک کامپوننت مورد نیاز هستند، بر عناصر مجاور تأثیر نمیگذارند، نیازی به تحمل بازسازی بخش مشخص نیست. مثالاً، وضعیت انیمیشن، فوکوس فیلد ورودی، موقعیت افقی پیمایش — مقدر است محلی نگه داشته شوند.
وقتی State Hoisting ضروری است: وضعیت توسط چند کامپوننت فرعی استفاده میشود؛ تغییر در یک فرعی باید در دیگری منعکس شود؛ منطق تغییر وضعیت باید جداگانه از UI آزموده شود؛ وضعیت باید در بازسازی Activity ذخیره شود. در این موارد، وضعیت محلی باعث تکرار و ناهماهنگی دادهها میشود.
رویکرد ترکیبی: حداقل وضعیت را محلی نگه داشته و باقی را بالا ببرید. قاعده Compose: «وضعیت را تا آنجا که لازم است بالا ببرید و تا آنجا که ممکن است پایین نگه دارید». در عمل، این به معنای شروع با remember محلی است، و تنها وقتی نیاز به دسترسی از کامپوننت دیگر پیدا شود — سطح را بالا ببرید. State Hoisting را احتیاطی انجام ندهید — این کد را بدون لزوم پیچیده میکند.
سوالات متداول
State Hoisting — الگویی در سطح کامپوننتهای UI است. ViewModel — لایه معماری برای منطق کسب و کار است. State Hoisting میتواند وضعیت را به سطح والد Composable، سطح صفحه یا ViewModel بالا ببرد. ViewModel — بالاترین نقطه بالا بردن برای وضعیتی است که باید از بازسازی Activity جان سالم به در آید.
کامپوننت Stateless با انتقال ساده مقادیر آزموده میشود. Composable را با پارامترهای مورد نیاز فراخوان کرده و نمایش را از طریق ComposeTestRule بررسی کنید. تغییر وضعیت در سطح والد یا ViewModel — جداگانه از UI آزموده میشود. این تستها را بسیار سادهتر میکند: نیازی به شبیهسازی بازسازی داخل کامپوننت نیست.
بله، این یک روش رایج است. اگر کامپوننت فقط باید دادهها را بدون امکان تغییر نمایش دهد — State<T> (فقط خواندنی) ارسال کنید. کامپوننت در تغییرات مشترک خواهد شد، اما نمیتواند آنها را ایجاد کند. این کپسولاسیون را تقویت کرده و از دادهها در برابر تغییرات نامطلوب محافظت میکند.
برای ارسال عمیق از CompositionLocal یا ارسال از طریق پارامترهای والد Composable استفاده کنید. اگر وضعیت در تمام صفحه مورد نیاز است — آن را به ViewModel منتقل کرده و از collectAsState() استفاده کنید. ارسال از طریق 5+ سطح نشانه معماری نادرست است؛ سلسله مراتب کامپوننتها را مرور کنید.
State Hoisting ممکن است تعداد بازسازیها را اندکی افزایش دهد، زیرا تغییر در والد میتواند همه فرعیها را بازسازی کند. برای فیلتر تغییرات از derivedStateOf و برای بهروزرسانی های نقطهای در LazyColumn از keys استفاده کنید. در اکثر سناریوها، سررایه State Hoisting در مقایسه با مزایای قابلیت حفظ ناچیز است.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید