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 ) }
برای پشتیبانی از عدم نیاز به ارسال رنگها بهعنوان وابستگی پارامتر صریح به اکثر ترکیبپذیرها، ComposeCompositionLocal را ارائه میدهد که به شما امکان میدهد اشیای نامگذاریشده با محدوده درختی ایجاد کنید که میتوانند بهعنوان روشی ضمنی برای جریان دادهها ازطریق درخت رابط کاربری استفاده شوند.
عناصر CompositionLocal معمولاً با مقداری در گره خاصی از درخت رابط کاربری ارائه میشوند. آن مقدار میتواند توسط فرزندان ترکیبپذیر آن بدون
اعلام CompositionLocal بهعنوان پارامتر در تابع ترکیبپذیر استفاده شود.
اصطلاحات کلیدی: در این راهنما، از اصطلاحات ترکیببندی، درخت واسط کاربر، و سلسلهمراتب واسط کاربر استفاده میکنیم. اگرچه ممکن است در راهنماهای دیگر بهجای یکدیگر استفاده شوند، اما معانی متفاوتی دارند:
- ترکیببندی عبارت است از ثبت نمودار فراخوانی توابع ترکیبپذیر.
- درخت واسط کاربر یا سلسلهمراتب واسط کاربر درختی از
LayoutNodeاست که توسط فرایند ترکیببندی ساخته، بهروز، و نگهداری میشود.
CompositionLocal چیزی است که زمینه Material در پشت صحنه استفاده میکند.
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 مرتبط میکند. content lambda از CompositionLocalProvider مقدار ارائهشده را هنگام دسترسی به ویژگی current از CompositionLocal دریافت خواهد کرد. وقتی مقدار جدیدی ارائه میشود، 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 ابزاری برای انتقال دادهها بهطور ضمنی در Composition است.
نشانه کلیدی دیگر برای استفاده از CompositionLocal زمانی است که پارامتر
متقاطع باشد و لایههای میانی پیادهسازی نباید از وجود آن مطلع باشند،
زیرا مطلع کردن آن لایههای میانی کاربرد ترکیبپذیر را محدود میکند. برای مثال، پُرسمان برای اجازههای Android
توسط CompositionLocal در پشت صحنه ارائه میشود. یک ترکیبپذیر انتخابگر رسانه
میتواند عملکرد جدیدی برای دسترسی به محتوای محافظتشده با اجازه در
دستگاه اضافه کند، بدون اینکه API آن تغییر کند و نیاز باشد فراخوانندگان انتخابگر رسانه
از این بافت اضافهشده که از محیط استفاده میشود مطلع باشند.
بااینحال، CompositionLocal همیشه بهترین راهحل نیست. استفاده بیشازحد از CompositionLocal را توصیه نمیکنیم زیرا معایبی دارد:
CompositionLocal باعث میشود استدلال درباره رفتار یک ترکیبپذیر سختتر شود. As
they create implicit dependencies, callers of composables that use them need
to make sure that a value for every CompositionLocal is satisfied.
علاوهبراین، ممکن است منبع حقیقت مشخصی برای این وابستگی وجود نداشته باشد زیرا میتواند در هر بخشی از «ترکیببندی» تغییر کند. بنابراین، اشکالزدایی برنامه هنگام بروز مشکل میتواند چالشبرانگیزتر باشد زیرا باید در «ترکیببندی» پیمایش کنید تا ببینید مقدار current کجا ارائه شده است. ابزارهایی مانند یافتن
موارد استفاده در IDE یا بازرس چیدمان Compose اطلاعات کافی
برای کاهش این مشکل ارائه میدهند.
تصمیم بگیرید که آیا میخواهید از CompositionLocal استفاده کنید یا نه
شرایط خاصی وجود دارد که میتواند CompositionLocal را به راهحل خوبی
برای مورد استفاده شما تبدیل کند:
CompositionLocal باید مقدار پیشفرض خوبی داشته باشد. اگر مقدار پیشفرضی وجود ندارد، باید تضمین کنید که برای توسعهدهنده بسیار دشوار است که در شرایطی قرار بگیرد که مقداری برای CompositionLocal ارائه نشده باشد.
ارائه نکردن مقدار پیشفرض میتواند هنگام ایجاد آزمایشها یا پیشدیدن یک ترکیبپذیر که از آن CompositionLocal استفاده میکند، مشکل و ناامیدی ایجاد کند، زیرا همیشه نیاز به ارائه صریح آن خواهد بود.
از CompositionLocal برای مفاهیمی که بهعنوان محدوده درختی یا
محدوده زیرسلسلهمراتبی درنظر گرفته نمیشوند استفاده نکنید. CompositionLocal زمانی منطقی است که بتواند
بهطور بالقوه توسط هر فرزند استفاده شود، نه فقط چند مورد از آنها.
اگر مورد استفاده شما این شرایط را برآورده نمیکند، قبلاز ایجاد
CompositionLocal، بخش
جایگزینهایی که باید درنظر بگیرید را بررسی کنید.
نمونهای از عملکرد بد، ایجاد یک CompositionLocal است که
ViewModel یک صفحه خاص را نگه میدارد تا همه composableها در آن صفحه بتوانند
یک مرجع به ViewModel برای انجام برخی منطقها دریافت کنند. این روش خوبی نیست
زیرا همه ترکیبپذیرهای زیر یک درخت میانای کاربر خاص نیازی به دانستن درباره
ViewModel ندارند. روش خوب این است که فقط اطلاعاتی را که قابلترکیبها نیاز دارند به آنها منتقل کنید،
با پیروی از این الگو که
وضعیت به پایین جریان مییابد و رویدادها به بالا جریان مییابند.
این رویکرد باعث میشود که ترکیبپذیرهای شما بیشتر قابلاستفاده مجدد باشند و آزمایش آنها آسانتر شود.
ایجاد CompositionLocal
دو میانای برنامهسازی کاربردی برای ایجاد CompositionLocal وجود دارد:
compositionLocalOf: تغییر مقدار ارائهشده درطول بازترکیب، فقط محتوایی را که مقدارcurrentآن را میخواند نامعتبر میکند.staticCompositionLocalOf: برخلافcompositionLocalOf، خواندنهایstaticCompositionLocalOfتوسط Compose ردیابی نمیشود. تغییر دادن مقدار باعث میشود کل لامبدا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 عادت خوبی است. توصیه میکنیم به composable فقط آنچه را نیاز دارد ارسال کنید. برای تشویق به جداسازی و استفاده مجدد از composableها، هر composable باید کمترین مقدار اطلاعات ممکن را نگه دارد.
@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، استفاده از وارونگی کنترل است. بهجای اینکه عنصر فرزند برای اجرای منطقی خاص وابستگی را دریافت کند، عنصر ولی این کار را انجام میدهد.
مثال زیر را ببینید که در آن یک فرزند باید درخواست بار کردن برخی دادهها را راهاندازی کند:
@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() } }
توصیهشده برای شما
- توجه: وقتی جاوا اسکریپت خاموش باشد، نوشتار پیوند نمایش داده میشود
- کالبدشناسی زمینه در Compose
- استفاده از «نماها» در Compose
- Kotlin برای Jetpack Compose