CompositionLocal ابزاری برای
انتقال دادهها بهصورت ضمنی ازطریق «ترکیب» است. در این صفحه، با جزئیات بیشتری درباره CompositionLocal آشنا میشوید، نحوه ایجاد CompositionLocal خودتان را یاد میگیرید، و متوجه میشوید که آیا CompositionLocal راهحل خوبی برای مورد استفاده شما است یا نه.
مقدمهای بر CompositionLocal
معمولاً در Compose، جریان دادهها از بالا به پایین ازطریق درخت واسط کاربر بهعنوان پارامتر به هر تابع ترکیبشدنی میرسد. این کار وابستگیهای عنصر ترکیبی را صریح میکند. بااینحال، این کار برای دادههایی که بسیار مکرر و بهطور گسترده استفاده میشوند، مانند رنگها یا سبکهای نوع، میتواند دشوار باشد. مثال زیر را ببینید:
@Composable fun MyApp() { // Theme information tends to be defined near the root of the application val colors = colors() } // Some composable deep in the hierarchy @Composable fun SomeTextLabel(labelText: String) { Text( text = labelText, color = colors.onPrimary // ← need to access colors here ) }
برای پشتیبانی از عدم نیاز به انتقال رنگها بهعنوان وابستگی پارامتر صریح به
اکثر عناصر ترکیبپذیر، Compose CompositionLocal را ارائه میدهد که به شما امکان میدهد
اشیاء نامگذاریشده با محدوده درختی ایجاد کنید که میتوانند بهعنوان روشی ضمنی برای
جریان دادهها در درخت واسط کاربر استفاده شوند.
CompositionLocal عنصر معمولاً با مقداری در گره خاصی از درخت واسط کاربر ارائه میشود. فرزندان ترکیبپذیر آن میتوانند بدون تعریف CompositionLocal بهعنوان پارامتر در تابع ترکیبپذیر از این مقدار استفاده کنند.
اصطلاحات کلیدی: در این راهنما، از اصطلاحات ترکیب، درخت رابط کاربری، و سلسله مراتب رابط کاربری استفاده میکنیم. اگرچه ممکن است در راهنماهای دیگر بهجای یکدیگر استفاده شوند، اما معانی متفاوتی دارند:
- ترکیب سابقه نمودار تماس توابع ترکیبی است.
- درخت واسط کاربر یا سلسله مراتب واسط کاربر درخت
LayoutNodeساختهشده، بهروزرسانیشده، و نگهداریشده توسط فرایند ترکیب است.
CompositionLocal چیزی است که زمینه «ماتریال» در پشت صحنه از آن استفاده میکند.
MaterialTheme شیئی است که سه نمونه CompositionLocal را ارائه میدهد: colorScheme،
typography، و shapes، که به شما امکان میدهد آنها را بعداً در هر بخش فرزند «قطعه» بازیابی کنید.
بهطور دقیق، اینها LocalColorScheme، LocalShapes، و
LocalTypography داراییهایی هستند که میتوانید ازطریق MaterialTheme
colorScheme، shapes، و typography مشخصهها به آنها دسترسی داشته باشید.
@Composable fun MyApp() { // Provides a Theme whose values are propagated down its `content` MaterialTheme { // New values for colorScheme, typography, and shapes are available // in MaterialTheme's content lambda. // ... content here ... } } // Some composable deep in the hierarchy of MaterialTheme @Composable fun SomeTextLabel(labelText: String) { Text( text = labelText, // `primary` is obtained from MaterialTheme's // LocalColors CompositionLocal color = MaterialTheme.colorScheme.primary ) }
نمونه CompositionLocal به بخشی از «ترکیب» محدود میشود، بنابراین میتوانید
مقادیر مختلفی را در سطوح مختلف درخت ارائه دهید. مقدار current
یک CompositionLocal با نزدیکترین مقدار ارائهشده توسط
یک جد در آن بخش از «ترکیب» مطابقت دارد.
برای ارائه مقدار جدید به CompositionLocal، از
CompositionLocalProvider
و تابع میانوند provides
که کلید CompositionLocal را به value مرتبط میکند استفاده کنید. وقتی به دارایی current در CompositionLocal دسترسی پیدا میکنید، لامبدای content در CompositionLocalProvider مقدار ارائهشده را دریافت خواهد کرد. وقتی مقدار جدیدی ارائه میشود، Compose بخشهایی از «ترکیب» را که CompositionLocal را میخوانند دوباره ترکیب میکند.
بهعنوان نمونهای از این، LocalContentColor CompositionLocal حاوی
رنگ محتوای ترجیحی است که برای نوشتار و
نمادنگاری استفاده میشود تا اطمینان حاصل شود که با رنگ پسزمینه فعلی کنتراست دارد. در مثال زیر، از CompositionLocalProvider برای ارائه مقادیر مختلف برای بخشهای مختلف «قطعه موسیقی» استفاده شده است.
@Composable fun CompositionLocalExample() { MaterialTheme { // Surface provides contentColorFor(MaterialTheme.colorScheme.surface) by default // This is to automatically make text and other content contrast to the background // correctly. Surface { Column { Text("Uses Surface's provided content color") CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.primary) { Text("Primary color provided by LocalContentColor") Text("This Text also uses primary as textColor") CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.error) { DescendantExample() } } } } } } @Composable fun DescendantExample() { // CompositionLocalProviders also work across composable functions Text("This Text uses the error color now") }
CompositionLocalExample قابل ترکیب.در مثال آخر، CompositionLocal نمونه بهصورت داخلی توسط عناصر ترکیبی Material استفاده شده است. برای دسترسی به مقدار فعلی CompositionLocal،
از دارایی current
آن استفاده کنید. در مثال زیر، مقدار Context فعلی
LocalContext CompositionLocal که معمولاً در برنامههای Android استفاده میشود
برای قالببندی نوشتار استفاده میشود:
@Composable fun FruitText(fruitSize: Int) { // Get `resources` from the current value of LocalContext val resources = LocalContext.current.resources val fruitText = remember(resources, fruitSize) { resources.getQuantityString(R.plurals.fruit_title, fruitSize) } Text(text = fruitText) }
CompositionLocal خودتان را بسازید
CompositionLocal ابزاری برای انتقال دادهها بهصورت ضمنی ازطریق «ترکیب» است.
علامت کلیدی دیگر برای استفاده از CompositionLocal زمانی است که پارامتر
لایه میانی و متقاطع پیادهسازی نباید از وجود آن آگاه باشد، زیرا آگاه کردن آن لایههای میانی
کاربرد ترکیبشدنی را محدود میکند. برای مثال، پُرسمان برای اجازههای Android توسط CompositionLocal در پشت صحنه انجام میشود. ترکیبشدنی انتخابگر رسانه
میتواند بدون تغییر دادن API آن و بدون نیاز به اینکه تماسگیرندگان انتخابگر رسانه
از این بافت اضافه که از محیط استفاده میشود مطلع باشند،
عملکرد جدیدی برای دسترسی به محتوای محافظتشده با اجازه در دستگاه اضافه کند.
بااینحال، CompositionLocal همیشه بهترین راه حل نیست. ما استفاده بیشازحد از CompositionLocal را توصیه نمیکنیم زیرا معایبی دارد:
CompositionLocal باعث میشود استدلال درباره رفتار یک عنصر ترکیبی دشوارتر شود. ازآنجاییکه
وابستگیهای ضمنی ایجاد میکنند، فراخوانندگان عناصر ترکیبی که از آنها استفاده میکنند باید
مطمئن شوند که مقدار هر CompositionLocal برآورده شده است.
علاوهبراین، ممکن است منبع حقیقت واضحی برای این وابستگی وجود نداشته باشد زیرا میتواند در هر بخشی از «ترکیب» تغییر کند. بنابراین، عیبیابی برنامه هنگام بروز مشکل میتواند چالشبرانگیزتر باشد زیرا باید در «ترکیب» پیمایش کنید تا ببینید مقدار current در کجا ارائه شده است. ابزارهایی مثل یافتن
کاربردها در IDE یا بازرس چیدمان «ترکیب» اطلاعات کافی برای کاهش این مشکل ارائه میدهند.
تصمیم بگیرید که از CompositionLocal استفاده کنید یا نه
شرایط خاصی وجود دارد که میتواند CompositionLocal را به راهحلی خوب برای مورد استفاده شما تبدیل کند:
CompositionLocal باید مقدار پیشفرض خوبی داشته باشد. اگر مقدار پیشفرضی وجود ندارد، باید تضمین کنید که توسعهدهنده بهسختی میتواند در شرایطی قرار بگیرد که مقداری برای CompositionLocal ارائه نشده باشد.
عدم ارائه مقدار پیشفرض میتواند هنگام ایجاد آزمایشها یا پیشنمایش یک عنصر ترکیبی که از آن استفاده میکند، باعث بروز مشکلات و ناامیدی شود. CompositionLocal همیشه نیاز دارد که بهطور صریح ارائه شود.
برای مفاهیمی که بهعنوان درختمحدوده یا
زیر سلسلهمراتب محدوده درنظر گرفته نمیشوند، از CompositionLocal استفاده نکنید. CompositionLocal زمانی منطقی است که بتواند
بهطور بالقوه توسط هر نوادهای استفاده شود، نه توسط چند نفر از آنها.
اگر مورد استفاده شما این الزامات را برآورده نمیکند، قبلاز ایجاد
CompositionLocal، بخش
جایگزینهایی که باید درنظر بگیرید را بررسی کنید.
برای مثال، ایجاد CompositionLocal که ViewModel صفحه خاصی را در خود نگه میدارد رویه بدی است، زیرا در این صورت همه عناصر ترکیبپذیر در آن صفحه میتوانند برای اجرای منطق خاصی به ViewModel ارجاع دهند. این روش مناسبی نیست
زیرا همه عناصر ترکیبپذیر زیر درخت میانای کاربر خاصی نباید درباره
ViewModel بدانند. روال خوب این است که فقط اطلاعاتی را که
ترکیبپذیرها نیاز دارند به آنها منتقل کنید و از الگوی
جریان وضعیت به پایین و جریان رویداد به بالا پیروی کنید.
این رویکرد باعث میشود عناصر ترکیبی شما قابلیت استفاده مجدد بیشتری داشته باشند و آزمایش آنها آسانتر شود.
ایجاد CompositionLocal
دو میانای برنامهسازی کاربردی برای ایجاد CompositionLocal وجود دارد:
compositionLocalOf: تغییر مقدار ارائهشده درطول ترکیب مجدد فقط محتوایی را که مقدارcurrentآن را میخواند نامعتبر میکند.staticCompositionLocalOf: برخلافcompositionLocalOf، خواندنstaticCompositionLocalOfتوسط «نوشتن» ردیابی نمیشود. تغییر مقدار باعث میشود کل لامبدایcontentکه در آنCompositionLocalارائه شده است بهجای اینکه فقط مکانهایی که مقدارcurrentدر «ترکیب» خوانده میشود دوباره ترکیب شود.
اگر احتمال تغییر مقدار ارائهشده به CompositionLocal بسیار کم است یا
هرگز تغییر نمیکند، از staticCompositionLocalOf برای بهرهمندی از مزایای عملکرد استفاده کنید.
برای مثال، سیستم طراحی یک برنامه ممکن است در نحوه استفاده از سایه برای عنصر رابط کاربری و برجسته کردن عناصر ترکیبی نظر خاصی داشته باشد. ازآنجاییکه ارتفاعهای مختلف برای برنامه باید در سراسر درخت میانای کاربر منتشر شود، از CompositionLocal استفاده میکنیم. ازآنجاییکه مقدار CompositionLocal بهصورت شرطی براساس زمینه سیستم استخراج میشود، از میانای برنامهسازی کاربردی compositionLocalOf استفاده میکنیم:
// LocalElevations.kt file data class Elevations(val card: Dp = 0.dp, val default: Dp = 0.dp) // Define a CompositionLocal global object with a default // This instance can be accessed by all composables in the app val LocalElevations = compositionLocalOf { Elevations() }
ارائه مقادیر به CompositionLocal
CompositionLocalProviderمقادیر را به نمونههای CompositionLocal برای سلسلهمراتب
دادهشده پیوند میدهد. برای ارائه مقدار جدید به CompositionLocal، از تابع
provides
میانوندی استفاده کنید که کلید CompositionLocal را به value بهصورت زیر مرتبط میکند:
// MyActivity.kt file class MyActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { // Calculate elevations based on the system theme val elevations = if (isSystemInDarkTheme()) { Elevations(card = 1.dp, default = 1.dp) } else { Elevations(card = 0.dp, default = 0.dp) } // Bind elevation as the value for LocalElevations CompositionLocalProvider(LocalElevations provides elevations) { // ... Content goes here ... // This part of Composition will see the `elevations` instance // when accessing LocalElevations.current } } } }
درحال مصرف CompositionLocal
CompositionLocal.current مقدار ارائهشده توسط نزدیکترین
CompositionLocalProvider را برمیگرداند که مقداری را برای آن CompositionLocal ارائه میدهد:
@Composable fun SomeComposable() { // Access the globally defined LocalElevations variable to get the // current Elevations in this part of the Composition MyCard(elevation = LocalElevations.current.card) { // Content } }
جایگزینهایی که باید درنظر گرفت
CompositionLocal ممکن است برای برخی موارد استفاده راهحل افراطی باشد. اگر مورد استفاده شما با معیارهای مشخصشده در بخش تصمیمگیری درباره استفاده از
CompositionLocal مطابقت ندارد، احتمالاً راهحل دیگری برای مورد استفاده شما مناسبتر است.
پارامترهای صریح را ارسال کنید
صریح بودن درباره وابستگیهای عنصر ترکیبی عادت خوبی است. توصیه میکنیم که ترکیبپذیرها را فقط به آنچه نیاز دارندارسال کنید. برای تشویق به جداسازی و استفاده مجدد از عناصر ترکیبی، هر عنصر ترکیبی باید کمترین مقدار ممکن از اطلاعات را در خود داشته باشد.
@Composable fun MyComposable(myViewModel: MyViewModel = viewModel()) { // ... MyDescendant(myViewModel.data) } // Don't pass the whole object! Just what the descendant needs. // Also, don't pass the ViewModel as an implicit dependency using // a CompositionLocal. @Composable fun MyDescendant(myViewModel: MyViewModel) { /* ... */ } // Pass only what the descendant needs @Composable fun MyDescendant(data: DataToDisplay) { // Display data }
وارونگی کنترل
روش دیگر برای جلوگیری از انتقال وابستگیهای غیرضروری به یک عنصر ترکیبی، استفاده از وارونگی کنترل است. بهجای اینکه فرزند برای اجرای منطق خاصی وابستگی داشته باشد، والد این کار را انجام میدهد.
مثال زیر را ببینید که در آن یک فرزند باید درخواست بار کردن برخیاز دادهها را راهاندازی کند:
@Composable fun MyComposable(myViewModel: MyViewModel = viewModel()) { // ... MyDescendant(myViewModel) } @Composable fun MyDescendant(myViewModel: MyViewModel) { Button(onClick = { myViewModel.loadData() }) { Text("Load data") } }
بسته به مورد، MyDescendant ممکن است مسئولیت زیادی داشته باشد. همچنین،
گذراندن MyViewModel بهعنوان وابستگی باعث میشود MyDescendant کمتر قابل استفاده مجدد باشد زیرا
اکنون با هم جفت شدهاند. جایگزینی را درنظر بگیرید که وابستگی را به فرزند منتقل نمیکند و از اصول وارونگی کنترل استفاده میکند که باعث میشود جد مسئول اجرای منطق باشد:
@Composable fun MyComposable(myViewModel: MyViewModel = viewModel()) { // ... ReusableLoadDataButton( onLoadClick = { myViewModel.loadData() } ) } @Composable fun ReusableLoadDataButton(onLoadClick: () -> Unit) { Button(onClick = onLoadClick) { Text("Load data") } }
این رویکرد میتواند برای برخی موارد استفاده مناسبتر باشد زیرا فرزند را از اجداد مستقیمش جدا میکند. ترکیبپذیرهای جد تمایل دارند پیچیدهتر شوند تا ترکیبپذیرهای سطح پایین انعطافپذیرتری داشته باشند.
بههمین ترتیب، از لامبداهای محتوای @Composable میتوان به همان روش برای دریافت مزایای یکسان استفاده کرد:
@Composable fun MyComposable(myViewModel: MyViewModel = viewModel()) { // ... ReusablePartOfTheScreen( content = { Button( onClick = { myViewModel.loadData() } ) { Text("Confirm") } } ) } @Composable fun ReusablePartOfTheScreen(content: @Composable () -> Unit) { Column { // ... content() } }
توصیهشده برای شما
- توجه: نوشتار پیوند وقتی جاوا اسکریپت خاموش است نمایش داده میشود
- آناتومی زمینه در «نوشتن»
- استفاده از «نماها» در «نوشتن»
- Kotlin برای Jetpack Compose