CompositionLocal
ابزاری برای انتقال داده ها از طریق Composition به طور ضمنی است. در این صفحه، با جزئیات بیشتری یاد خواهید گرفت که CompositionLocal
چیست، چگونه CompositionLocal
خود را ایجاد کنید، و بدانید که آیا CompositionLocal
راه حل خوبی برای موارد استفاده شما است یا خیر.
معرفی CompositionLocal
معمولاً در Compose، داده ها از طریق درخت UI به عنوان پارامترهای هر تابع قابل ترکیب به پایین جریان می یابد . این امر وابستگی های یک composable را واضح می کند. با این حال، این ممکن است برای دادههایی که اغلب و به طور گسترده استفاده میشوند، مانند رنگها یا سبکهای تایپ، دشوار باشد. مثال زیر را ببینید:
@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
ارائه میکند که به شما امکان میدهد اشیاء نامگذاری شده با دامنه درختی ایجاد کنید که میتواند به عنوان یک روش ضمنی برای داشتن جریان داده از طریق درخت UI استفاده شود.
عناصر CompositionLocal
معمولاً با یک مقدار در گره خاصی از درخت UI ارائه می شوند. این مقدار می تواند توسط نوادگان قابل ترکیب آن بدون اعلام CompositionLocal
به عنوان یک پارامتر در تابع composable استفاده شود.
CompositionLocal
چیزی است که تم Material در زیر هود استفاده می کند. MaterialTheme
شیای است که سه نمونه CompositionLocal
را ارائه میکند: colorScheme
، typography
و shapes
، که به شما امکان میدهد بعداً آنها را در هر قسمتی از Composition بازیابی کنید. به طور خاص، اینها ویژگیهای 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
به بخشی از Composition اختصاص داده شده است تا بتوانید مقادیر مختلفی را در سطوح مختلف درخت ارائه دهید. مقدار current
یک CompositionLocal
با نزدیکترین مقدار ارائه شده توسط یک اجداد در آن قسمت از Composition مطابقت دارد.
برای ارائه یک مقدار جدید به CompositionLocal
، از CompositionLocalProvider
و تابع infix provides
که یک کلید CompositionLocal
را به یک value
مرتبط می کند، استفاده کنید. لامبدای content
CompositionLocalProvider
هنگام دسترسی به ویژگی current
CompositionLocal
مقدار ارائه شده را دریافت می کند. وقتی مقدار جدیدی ارائه میشود، Compose قسمتهایی از Composition را که CompositionLocal
میخوانند، دوباره ترکیب میکند.
به عنوان مثالی از این، LocalContentColor
CompositionLocal
حاوی رنگ محتوای ترجیحی مورد استفاده برای متن و نماد نگاری است تا از تضاد آن با رنگ پسزمینه فعلی اطمینان حاصل کند. در مثال زیر، CompositionLocalProvider
برای ارائه مقادیر مختلف برای بخشهای مختلف Composition استفاده میشود.
@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") }
شکل 1. پیش نمایش CompositionLocalExample
composable.
در آخرین مثال، نمونههای CompositionLocal
به صورت داخلی توسط مواد composable استفاده میشوند. برای دسترسی به مقدار فعلی یک 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
رفتار یک composable را برای استدلال سخت تر می کند . همانطور که وابستگی های ضمنی ایجاد می کنند، فراخواننده های composable که از آنها استفاده می کنند باید مطمئن شوند که یک مقدار برای هر CompositionLocal
برآورده شده است.
علاوه بر این، ممکن است هیچ منبع روشنی از حقیقت برای این وابستگی وجود نداشته باشد زیرا می تواند در هر بخشی از ترکیب جهش پیدا کند. بنابراین، اشکالزدایی برنامه در هنگام بروز مشکل میتواند چالشبرانگیزتر باشد ، زیرا باید در Composition حرکت کنید تا ببینید مقدار current
کجا ارائه شده است. ابزارهایی مانند Find usages در IDE یا Compose layout inspector اطلاعات کافی برای کاهش این مشکل ارائه می دهند.
تصمیم گیری در مورد استفاده از CompositionLocal
شرایط خاصی وجود دارد که می تواند CompositionLocal
به یک راه حل خوب برای موارد استفاده شما تبدیل کند:
یک CompositionLocal
باید مقدار پیش فرض خوبی داشته باشد . اگر مقدار پیشفرض وجود ندارد، باید تضمین کنید که برای یک توسعهدهنده بسیار دشوار است که در موقعیتی قرار بگیرد که در آن مقداری برای CompositionLocal
ارائه نشده باشد. عدم ارائه یک مقدار پیشفرض میتواند باعث ایجاد مشکل و ناامیدی در هنگام ایجاد آزمایش یا پیشنمایش یک composable شود که از آن CompositionLocal
استفاده میکند، همیشه باید به طور صریح ارائه شود.
از CompositionLocal
برای مفاهیمی که به عنوان محدوده درختی یا زیر سلسله مراتبی تصور نمی شوند، اجتناب کنید . یک CompositionLocal
زمانی معنا پیدا میکند که بتوان آن را به طور بالقوه توسط هر نسلی استفاده کرد، نه تعداد کمی از آنها.
اگر مورد استفاده شما این الزامات را برآورده نمی کند، قبل از ایجاد CompositionLocal
بخش Alternatives to consider را بررسی کنید.
یک مثال از یک عمل بد، ایجاد یک CompositionLocal
است که ViewModel
یک صفحه خاص را نگه میدارد، به طوری که همه اجزای سازنده در آن صفحه میتوانند برای اجرای منطقی به ViewModel
ارجاع دهند. این یک روش بد است زیرا همه اجزای سازنده زیر یک درخت UI خاص نیازی به دانستن یک ViewModel
ندارند. روش خوب این است که فقط اطلاعاتی را که آنها به ترکیبپذیرها نیاز دارند، با پیروی از الگویی که حالت پایین میآید و رویدادها به سمت بالا میروند، منتقل میکنند. این رویکرد باعث میشود که کامپوزیشنهای شما قابل استفاده مجدد و آزمایش آسانتر باشند.
ایجاد CompositionLocal
دو API برای ایجاد CompositionLocal
وجود دارد:
compositionLocalOf
: تغییر مقدار ارائه شده در حین ترکیب مجدد، فقط محتوایی را که مقدارcurrent
آن را میخواند، بی اعتبار میکند.staticCompositionLocalOf
: برخلافcompositionLocalOf
، خواندنstaticCompositionLocalOf
توسط Compose ردیابی نمیشود. تغییر مقدار باعث می شود که کلcontent
لامبدا که در آنCompositionLocal
ارائه شده است، به جای مکان هایی که مقدارcurrent
در Composition خوانده می شود، دوباره ترکیب شود.
اگر مقدار ارائه شده به CompositionLocal
بسیار بعید است که تغییر کند یا هرگز تغییر نخواهد کرد، از staticCompositionLocalOf
برای دریافت مزایای عملکرد استفاده کنید.
به عنوان مثال، سیستم طراحی یک برنامه ممکن است به روشی که ترکیبپذیرها با استفاده از یک سایه برای مؤلفه UI بالا میروند، مورد ارزیابی قرار گیرد. از آنجایی که ارتفاعات مختلف برنامه باید در سراسر درخت رابط کاربری منتشر شود، از CompositionLocal
استفاده میکنیم. از آنجایی که مقدار CompositionLocal
به صورت مشروط بر اساس موضوع سیستم مشتق می شود، ما از compositionLocalOf
API استفاده می کنیم:
// 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
Composible CompositionLocalProvider
مقادیر را به نمونه های CompositionLocal
برای سلسله مراتب داده شده متصل می کند . برای ارائه یک مقدار جدید به CompositionLocal
، از تابع provides
infix استفاده کنید که یک کلید 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 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
content lambdas را می توان به همان روش برای دریافت مزایای مشابه استفاده کرد :
@Composable fun MyComposable(myViewModel: MyViewModel = viewModel()) { // ... ReusablePartOfTheScreen( content = { Button( onClick = { myViewModel.loadData() } ) { Text("Confirm") } } ) } @Composable fun ReusablePartOfTheScreen(content: @Composable () -> Unit) { Column { // ... content() } }{% کلمه به کلمه %}
برای شما توصیه می شود
- توجه: وقتی جاوا اسکریپت خاموش است، متن پیوند نمایش داده می شود
- آناتومی یک تم در Compose
- استفاده از Views در Compose
- Kotlin برای Jetpack Compose