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
كمَعلمة في الدالة القابلة للتجميع.
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
. ستحصل دالة lambda
content
في CompositionLocalProvider
على القيمة
المقدَّمة عند الوصول إلى سمة current
في CompositionLocal
. عند تقديم قيمة
جديدة، يعيد تطبيق "الإنشاء" تركيب أجزاء من التركيب التي تقرأ
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") }
الشكل 1: معاينة العنصر القابل للتجميع 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
في الخلفية. يمكن أن تضيف أداة اختيار الوسائط القابلة للتركيب
وظائف جديدة للوصول إلى المحتوى المحمي بإذن على
الجهاز بدون تغيير واجهة برمجة التطبيقات الخاصة بها ومطالبة مُستخدِمي أداة اختيار الوسائط
بمعرفة هذا السياق المُضاف المستخدَم من البيئة.
ومع ذلك، لا يُعدّ CompositionLocal
الحلّ الأفضل دائمًا. ننصح بعدم إساءة استخدام CompositionLocal
لأنّ ذلك قد يؤدي إلى بعض النتائج السلبية:
CompositionLocal
يجعل من الصعب تفسير سلوك العنصر القابل للتجميع. وبما أنّها تُنشئ تبعيات ضمنية، على مُستخدِمي العناصر القابلة للتجميع الذين يستخدمونها
التأكّد من توفّر قيمة لكلّ CompositionLocal
.
بالإضافة إلى ذلك، قد لا يتوفّر مصدر واضح لهذه التبعية لأنّه
يمكن أن تتغيّر في أي جزء من التركيب. وبالتالي، قد يكون تصحيح أخطاء التطبيق عند حدوث
مشكلة أكثر صعوبة لأنّك تحتاج إلى الانتقال إلى أعلى
التركيبة لمعرفة مكان تقديم قيمة current
. توفّر أدوات مثل العثور على موارد الاستعمال في IDE أو أداة فحص تنسيق الإنشاء معلومات كافية للتخفيف من هذه المشكلة.
تحديد ما إذا كنت تريد استخدام CompositionLocal
هناك شروط معيّنة يمكن أن تجعل CompositionLocal
حلًا جيدًا
لحالة الاستخدام:
يجب أن تحتوي سمة CompositionLocal
على قيمة تلقائية جيدة. إذا لم تكن هناك قيمة CompositionLocal
تلقائية، يجب التأكّد من أنّه من الصعب جدًا على المطوّر أن يقع في موقف لا يتم فيه تقديم قيمة لسمة CompositionLocal
.
يمكن أن يؤدي عدم تقديم قيمة تلقائية إلى حدوث مشاكل وإزعاج عند إنشاء
اختبارات أو معاينة عنصر قابل للتجميع يستخدم CompositionLocal
، لأنّه سيتطلب دائمًا
تقديمها صراحةً.
تجنَّب استخدام CompositionLocal
للعناصر التي لا تُعتبر على مستوى الشجرة أو
على مستوى التسلسل الهرمي الفرعي. يكون استخدام CompositionLocal
منطقيًا عندما يمكن
استخدامه من قِبل أي نسل، وليس من قِبل بضعة أنسال.
إذا لم تستوفِ حالة الاستخدام هذه المتطلبات، اطّلِع على القسم
البدائل التي يجب أخذها في الاعتبار قبل إنشاء
CompositionLocal
.
من الأمثلة على الممارسات غير الصحيحة إنشاء CompositionLocal
يحتوي على
ViewModel
شاشة معيّنة لكي تتمكّن كل العناصر القابلة للتجميع في تلك الشاشة من
الحصول على مرجع إلى 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 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() } }
أفلام مُقترَحة لك
- ملاحظة: يتم عرض نص الرابط عندما تكون لغة JavaScript غير مفعّلة.
- تحليل بنية المظهر في ميزة "الإنشاء"
- استخدام "طرق العرض" في ميزة "الإنشاء"
- Kotlin لـ Jetpack Compose