CompositionLocal
เป็นเครื่องมือสำหรับ
ส่งข้อมูลผ่าน Composition โดยนัย ในหน้านี้ คุณจะได้
เรียนรู้รายละเอียดเพิ่มเติมเกี่ยวกับCompositionLocal
วิธีสร้างCompositionLocal
ของคุณเอง และดูว่าCompositionLocal
เป็นโซลูชันที่ดีสำหรับ
กรณีการใช้งานของคุณหรือไม่
ขอแนะนำ CompositionLocal
โดยปกติใน Compose ข้อมูลจะไหลลงผ่าน แผนผัง UI เป็นพารามิเตอร์ของแต่ละฟังก์ชันที่ใช้ Compose ได้ ซึ่งทำให้การอ้างอิงของ 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 เพื่อรองรับการไม่ต้องส่งสีเป็นพารามิเตอร์ที่ขึ้นอย่างชัดเจนไปยัง Composable ส่วนใหญ่
โดยปกติแล้ว องค์ประกอบ CompositionLocal
จะมีค่าในโหนดหนึ่งๆ
ของทรี UI ลูกหลานที่ประกอบได้จะใช้ค่าดังกล่าวได้โดยไม่ต้อง
ประกาศ CompositionLocal
เป็นพารามิเตอร์ในฟังก์ชันที่ประกอบได้
CompositionLocal
คือสิ่งที่ธีม Material ใช้เบื้องหลัง
MaterialTheme
คือ
ออบเจ็กต์ที่มีอินสแตนซ์ 3 รายการของ 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
มีขอบเขตเป็นส่วนหนึ่งขององค์ประกอบ คุณจึงระบุค่าที่แตกต่างกันในระดับต่างๆ ของโครงสร้างต้นไม้ได้ ค่า current
ของ CompositionLocal
จะสอดคล้องกับค่าที่ใกล้ที่สุดซึ่งระบุโดย
องค์ประกอบระดับบนสุดในส่วนนั้นขององค์ประกอบ
หากต้องการระบุค่าใหม่ให้กับ CompositionLocal
ให้ใช้
CompositionLocalProvider
และฟังก์ชัน provides
infix ที่เชื่อมโยงคีย์ CompositionLocal
กับ value
Lambda ของ CompositionLocalProvider
จะได้รับค่าที่ระบุเมื่อเข้าถึงพร็อพเพอร์ตี้ current
ของ CompositionLocal
content
เมื่อมีการระบุ
ค่าใหม่ Compose จะสร้างองค์ประกอบบางส่วนของ Composition ที่อ่าน
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
ที่เขียนด้วย Compose ได้
ในตัวอย่างสุดท้าย CompositionLocal
อินสแตนซ์ถูกใช้ภายใน
โดย Composable ของ 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
เบื้องหลัง Composable ของเครื่องมือเลือกสื่อ
สามารถเพิ่มฟังก์ชันใหม่เพื่อเข้าถึงเนื้อหาที่ได้รับการปกป้องด้วยสิทธิ์ใน
อุปกรณ์โดยไม่ต้องเปลี่ยน API และกำหนดให้ผู้เรียกใช้เครื่องมือเลือกสื่อ
ต้องทราบบริบทเพิ่มเติมนี้ที่ใช้จากสภาพแวดล้อม
อย่างไรก็ตาม CompositionLocal
ไม่ใช่โซลูชันที่ดีที่สุดเสมอไป เราไม่แนะนำให้ใช้ CompositionLocal
มากเกินไปเนื่องจากมีข้อเสียบางประการดังนี้
CompositionLocal
ทำให้พฤติกรรมของ Composable คาดเดาได้ยากขึ้น เนื่องจากสร้างการอ้างอิงโดยนัย ผู้เรียกใช้ Composable ที่ใช้ค่าเหล่านี้จึงต้องตรวจสอบว่าค่าสำหรับ CompositionLocal
ทุกรายการเป็นไปตามข้อกำหนด
นอกจากนี้ อาจไม่มีแหล่งข้อมูลที่ชัดเจนสำหรับ Dependency นี้เนื่องจาก Dependency สามารถเปลี่ยนแปลงได้ในส่วนใดก็ได้ของ Composition ดังนั้น การแก้ไขข้อบกพร่องของแอปเมื่อเกิดปัญหาจึงอาจเป็นเรื่องที่ท้าทายมากขึ้น เนื่องจากคุณต้องไปยังส่วนต่างๆ ของ
Composition เพื่อดูว่ามีการระบุค่า current
ไว้ที่ใด เครื่องมือต่างๆ เช่น Find
usages ใน IDE หรือCompose layout inspector จะให้ข้อมูลเพียงพอที่จะ
ลดปัญหานี้
การตัดสินใจว่าจะใช้ CompositionLocal
CompositionLocal
เป็นโซลูชันที่ดีสำหรับกรณีการใช้งานของคุณได้ในบางเงื่อนไข ดังนี้
CompositionLocal
ควรมีค่าเริ่มต้นที่ดี หากไม่มีค่าเริ่มต้น คุณต้องรับประกันว่านักพัฒนาแอปจะเข้าสู่สถานการณ์ที่ไม่ได้ระบุค่าสำหรับ CompositionLocal
ได้ยากมาก
การไม่ระบุมูลค่าเริ่มต้นอาจทำให้เกิดปัญหาและความไม่พอใจเมื่อสร้าง
การทดสอบหรือแสดงตัวอย่าง Composable ที่ใช้ CompositionLocal
ซึ่งจะต้องระบุมูลค่าอย่างชัดเจนเสมอ
หลีกเลี่ยงการใช้ CompositionLocal
สำหรับแนวคิดที่ไม่ได้ถือว่าเป็นขอบเขตระดับต้นไม้หรือขอบเขตระดับลำดับชั้นย่อย CompositionLocal
จะมีประโยชน์เมื่อลูกหลานทุกคนมีโอกาสใช้
ไม่ใช่แค่บางคน
หากกรณีการใช้งานของคุณไม่เป็นไปตามข้อกำหนดเหล่านี้ โปรดดูส่วนทางเลือกที่ควรพิจารณาก่อนสร้างCompositionLocal
ตัวอย่างของแนวทางที่ไม่ดีคือการสร้าง CompositionLocal
ที่มี
ViewModel
ของหน้าจอหนึ่งๆ เพื่อให้ Composable ทั้งหมดในหน้าจอนั้นสามารถ
รับข้อมูลอ้างอิงไปยัง ViewModel
เพื่อดำเนินการตรรกะบางอย่าง นี่เป็นแนวทางที่ไม่ดี
เนื่องจาก Composable ทั้งหมดที่อยู่ใต้ทรี UI ที่เฉพาะเจาะจงไม่จำเป็นต้องทราบเกี่ยวกับ
ViewModel
แนวทางปฏิบัติที่ดีคือการส่งเฉพาะข้อมูลที่ Composable ต้องการ
ตามรูปแบบที่สถานะไหลลงและเหตุการณ์ไหลขึ้น วิธีนี้จะช่วยให้ Composable ของคุณนำกลับมาใช้ซ้ำได้มากขึ้นและทดสอบได้ง่ายขึ้น
การสร้าง CompositionLocal
มี API 2 รายการสำหรับสร้าง CompositionLocal
ดังนี้
compositionLocalOf
: การเปลี่ยนค่าที่ระบุระหว่างการจัดองค์ประกอบใหม่จะทำให้เนื้อหาที่อ่านค่าcurrent
เป็นโมฆะเท่านั้นstaticCompositionLocalOf
การอ่านstaticCompositionLocalOf
จะไม่ได้รับการติดตามโดย Compose ซึ่งแตกต่างจากcompositionLocalOf
การเปลี่ยนค่าจะทําให้content
Lambda ทั้งหมดที่มีCompositionLocal
ได้รับการเรียบเรียงใหม่ แทนที่จะเป็น เฉพาะที่ที่อ่านค่าcurrent
ในการเรียบเรียง
หากค่าที่ระบุใน CompositionLocal
ไม่น่าจะเปลี่ยนแปลงหรือไม่มีการเปลี่ยนแปลง ให้ใช้ staticCompositionLocalOf
เพื่อรับประโยชน์ด้านประสิทธิภาพ
ตัวอย่างเช่น ระบบการออกแบบของแอปอาจมีลักษณะเฉพาะในวิธีที่ยกระดับ Composable
โดยใช้เงาสำหรับคอมโพเนนต์ UI เนื่องจากระดับความสูงที่แตกต่างกันสำหรับแอปควรแพร่กระจายไปทั่วแผนผัง 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
Composable 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
อาจเป็นโซลูชันที่มากเกินไปสำหรับ Use Case บางอย่าง หากกรณีการใช้งานของคุณไม่เป็นไปตามเกณฑ์ที่ระบุไว้ในส่วนการตัดสินใจว่าจะใช้
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
นำกลับมาใช้ซ้ำได้น้อยลงเนื่องจาก
ตอนนี้ทั้ง 2 อย่างเชื่อมโยงกันแล้ว ลองพิจารณาทางเลือกอื่นที่ไม่ได้ส่ง
การอ้างอิงไปยังองค์ประกอบย่อย และใช้หลักการผกผันการควบคุมที่
ทําให้องค์ประกอบหลักมีหน้าที่รับผิดชอบในการเรียกใช้ตรรกะ
@Composable fun MyComposable(myViewModel: MyViewModel = viewModel()) { // ... ReusableLoadDataButton( onLoadClick = { myViewModel.loadData() } ) } @Composable fun ReusableLoadDataButton(onLoadClick: () -> Unit) { Button(onClick = onLoadClick) { Text("Load data") } }
วิธีนี้อาจเหมาะกับกรณีการใช้งานบางอย่างมากกว่าเนื่องจากแยกองค์ประกอบย่อยออกจากองค์ประกอบหลักที่อยู่ติดกัน Composable บรรพบุรุษมักจะมีความซับซ้อนมากขึ้น เพื่อรองรับ Composable ระดับล่างที่มีความยืดหยุ่นมากขึ้น
ในทำนองเดียวกัน คุณสามารถใช้ Lambda ของเนื้อหา @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