CompositionLocal
เป็นเครื่องมือสำหรับ
ส่งผ่านข้อมูลผ่านการเรียบเรียงโดยปริยาย ในหน้านี้ คุณจะ
ดูว่า CompositionLocal
มีรายละเอียดเพิ่มเติมอย่างไร รวมถึงวิธีสร้าง
CompositionLocal
และทราบว่า CompositionLocal
เป็นโซลูชันที่ดีสำหรับ
Use Case ของคุณ
ขอแนะนำ CompositionLocal
โดยปกติใน Compose จะมีข้อมูลไหลลงผ่าน แผนผัง UI เป็นพารามิเตอร์สำหรับแต่ละฟังก์ชันที่ประกอบกันได้ ซึ่งจะทำให้ Composable ทรัพยากร Dependency อย่างชัดเจน แต่ก็อาจสร้างความยุ่งยากสำหรับข้อมูลที่ซับซ้อน บ่อยครั้งและใช้กันอย่างแพร่หลาย เช่น สี หรือรูปแบบประเภท โปรดดูข้อมูลต่อไปนี้ ตัวอย่าง:
@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 ) }
เพื่อรองรับการไม่ต้องส่งสีเป็นทรัพยากร Dependency ของพารามิเตอร์อย่างชัดแจ้ง
Composable ส่วนใหญ่ Compose จะมี CompositionLocal
ซึ่งช่วยให้คุณสามารถ
เพื่อสร้างออบเจ็กต์ที่มีชื่อในขอบเขตระดับต้นไม้
ซึ่งใช้เพื่อแสดง
โฟลว์ข้อมูลผ่านแผนผัง UI
โดยปกติแล้วองค์ประกอบ CompositionLocal
จะใส่ค่าไว้ในโหนดบางโหนด
ของแผนผัง UI ค่านั้นสามารถนำมาใช้โดยองค์ประกอบสืบทอดที่ประกอบกันได้โดยไม่มี
ประกาศ CompositionLocal
เป็นพารามิเตอร์ในฟังก์ชัน Composable
CompositionLocal
คือสิ่งที่ธีม Material ใช้ภายใน
MaterialTheme
คือ
ออบเจ็กต์ที่มีอินสแตนซ์ CompositionLocal
3 รายการ ได้แก่ สี การพิมพ์
และรูปร่าง ซึ่งช่วยให้สามารถเรียกข้อมูลในภายหลังในส่วนที่สืบทอดมาของ
การเรียบเรียง ซึ่งก็คือ LocalColors
, LocalShapes
และ
พร็อพเพอร์ตี้ LocalTypography
ที่คุณเข้าถึงได้ผ่าน MaterialTheme
แอตทริบิวต์ colors
, shapes
และ typography
@Composable fun MyApp() { // Provides a Theme whose values are propagated down its `content` MaterialTheme { // New values for colors, 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.colors.primary ) }
อินสแตนซ์ CompositionLocal
จะกำหนดขอบเขตไว้ที่ส่วนใดส่วนหนึ่งของการเรียบเรียงเพื่อให้คุณ
สามารถระบุค่าที่แตกต่างกันในระดับต่างๆ ของโครงสร้าง ค่า current
ของ CompositionLocal
สอดคล้องกับค่าที่ใกล้เคียงที่สุดที่ระบุโดย
ระดับบนของการเรียบเรียงนั้น
หากต้องการระบุค่าใหม่ให้กับ CompositionLocal
ให้ใช้พารามิเตอร์
CompositionLocalProvider
และ provides
ฟังก์ชัน infix ที่เชื่อมโยงคีย์ CompositionLocal
กับ value
แลมบ์ดา content
จาก CompositionLocalProvider
จะได้รับข้อมูล
เมื่อเข้าถึงคุณสมบัติ current
ของ CompositionLocal
เมื่อ
มีการระบุค่าใหม่ "เขียน" จะจัดองค์ประกอบส่วนต่างๆ ของการเรียบเรียงที่อ่าน
CompositionLocal
ในตัวอย่างนี้ CompositionLocal
LocalContentAlpha
จะมีเนื้อหาอัลฟ่าที่ใช้สำหรับข้อความและ
ระบบการตีความสัญลักษณ์เพื่อเน้นหรือไม่สำคัญส่วนต่างๆ ของ UI ใน
ตัวอย่างต่อไปนี้ CompositionLocalProvider
ใช้เพื่อนำเสนอ
สำหรับส่วนต่างๆ ของการเรียบเรียง
@Composable fun CompositionLocalExample() { MaterialTheme { // MaterialTheme sets ContentAlpha.high as default Column { Text("Uses MaterialTheme's provided alpha") CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) { Text("Medium value provided for LocalContentAlpha") Text("This Text also uses the medium value") CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.disabled) { DescendantExample() } } } } } @Composable fun DescendantExample() { // CompositionLocalProviders also work across composable functions Text("This Text uses the disabled alpha now") }
รูปที่ 1 ตัวอย่างของ CompositionLocalExample
Composable
ในตัวอย่างทั้งหมดข้างต้น มีการใช้อินสแตนซ์ CompositionLocal
ภายใน
โดย Material Composables หากต้องการเข้าถึงค่าปัจจุบันของ 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
คือเมื่อพารามิเตอร์
ไม่ควรคํานึงถึงการใช้งานแบบข้ามคัตและระดับกลาง
มีอยู่จริง เนื่องจากการทำให้เลเยอร์กลางเหล่านั้นรับรู้ได้นั้นจะจำกัด
ของ Composable เช่น การค้นหาสิทธิ์ของ Android คือ
ในราคา CompositionLocal
ที่ซ่อนอยู่ เครื่องมือเลือกสื่อที่ประกอบกันได้
สามารถเพิ่มฟังก์ชันใหม่เพื่อเข้าถึงเนื้อหาที่ได้รับการคุ้มครองใน
โดยไม่เปลี่ยน API และไม่ต้องเรียกใช้เครื่องมือเลือกสื่อ
ต้องระวังบริบทเพิ่มเติมนี้ที่ใช้จากสภาพแวดล้อม
อย่างไรก็ตาม CompositionLocal
อาจไม่ใช่วิธีแก้ปัญหาที่ดีที่สุดเสมอไป พ
ไม่สนับสนุนให้ใช้ CompositionLocal
มากเกินไป เนื่องจากมีข้อเสียบางประการ เช่น
CompositionLocal
ทำให้ลักษณะการทำงานของ Composable ยากต่อการให้เหตุผล อาส
โมเดลเหล่านี้สร้างทรัพยากร Dependency โดยนัย ซึ่งเรียกใช้ Composable ที่จำเป็นต้องใช้
เพื่อให้แน่ใจว่าค่าของทุก CompositionLocal
เป็นที่น่าพอใจ
นอกจากนี้ ทรัพยากร Dependency นี้อาจไม่มีแหล่งที่มาที่ชัดเจนแน่นอน
ซึ่งอาจเปลี่ยนแปลงในส่วนใดก็ได้ของการเรียบเรียง ดังนั้น การแก้ไขข้อบกพร่องของแอปเมื่อ
ก็เป็นเรื่องที่ท้าทายยิ่งขึ้น เพราะคุณต้องสำรวจ
องค์ประกอบเพื่อดูตำแหน่งที่ระบุค่า current
เครื่องมือต่างๆ เช่น Find
ใน IDE หรือเครื่องมือตรวจสอบเลย์เอาต์ของ Compose จะให้ข้อมูลที่เพียงพอเพื่อ
บรรเทาปัญหานี้
กำลังตัดสินใจว่าจะใช้ 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
: สิ่งที่ต่างจากcompositionLocalOf
ตรงที่การอ่านstaticCompositionLocalOf
ไม่ต่างจาก Compose ติดตามอยู่ การเปลี่ยนค่าจะส่งผลให้เกิด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
CompositionLocalProvider
Composable จะเชื่อมโยงค่ากับอินสแตนซ์ 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 Card(elevation = LocalElevations.current.card) { // Content } }
ทางเลือกที่ควรพิจารณา
CompositionLocal
อาจเป็นโซลูชันที่มากเกินไปสำหรับกรณีการใช้งานบางกรณี หาก
ไม่เป็นไปตามเกณฑ์ที่ระบุไว้ในหัวข้อการตัดสินใจว่าจะใช้หรือไม่
ส่วน CompositionLocal โซลูชันอื่นน่าจะดีกว่านี้
เหมาะสำหรับกรณีการใช้งานของคุณ
ส่งพารามิเตอร์ที่ชัดแจ้ง
การมีความชัดเจนเกี่ยวกับทรัพยากร Dependency ของ Composable นั้นเป็นลักษณะนิสัยที่ดี คำแนะนำจากเรา ที่คุณส่งผ่าน Composables เฉพาะส่วนที่จำเป็น เพื่อกระตุ้นการแยกออกจากกัน และ 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 }
การกลับการควบคุม
อีกวิธีหนึ่งในการหลีกเลี่ยงการส่งผ่านทรัพยากร Dependency ที่ไม่จำเป็นไปยัง Composable คือ ผ่านการกลับด้านการควบคุม แทนที่องค์ประกอบสืบทอดจะพึ่งพา ใช้ตรรกะบางอย่าง แต่ระดับบนสุดจะดำเนินการกับตรรกะดังกล่าวแทน
ดูตัวอย่างต่อไปนี้ซึ่งองค์ประกอบสืบทอดต้องทริกเกอร์คำขอ โหลดข้อมูลบางส่วน:
@Composable fun MyComposable(myViewModel: MyViewModel = viewModel()) { // ... MyDescendant(myViewModel) } @Composable fun MyDescendant(myViewModel: MyViewModel) { Button(onClick = { myViewModel.loadData() }) { Text("Load data") } }
MyDescendant
อาจมีหน้าที่รับผิดชอบหลายอย่าง ซึ่งขึ้นอยู่กับแต่ละกรณี และ
การส่ง MyViewModel
เป็นทรัพยากร Dependency ทำให้ MyDescendant
ใช้งานซ้ำได้น้อยลงเนื่องจาก
มาผสานเข้าด้วยกัน ลองพิจารณาทางเลือกที่ไม่ผ่าน
Dependency ในองค์ประกอบสืบทอดและใช้การกลับกันของหลักการการควบคุม
ทำให้ระดับบนมีหน้าที่ประมวลผลตรรกะ:
@Composable fun MyComposable(myViewModel: MyViewModel = viewModel()) { // ... ReusableLoadDataButton( onLoadClick = { myViewModel.loadData() } ) } @Composable fun ReusableLoadDataButton(onLoadClick: () -> Unit) { Button(onClick = onLoadClick) { Text("Load data") } }
วิธีนี้อาจเหมาะกับกรณีการใช้งานบางกรณีมากกว่า เนื่องจากจะแยกส่วน ย่อยจากบรรพบุรุษโดยตรง Composable ของบรรพบุรุษมีแนวโน้มที่จะเพิ่ม ซับซ้อนเพื่อรองรับ Composable ระดับล่างที่ยืดหยุ่นกว่า
ในทำนองเดียวกัน @Composable
lambda ก็สามารถใช้ในลักษณะเดียวกันได้
สิทธิประโยชน์เดียวกัน
@Composable fun MyComposable(myViewModel: MyViewModel = viewModel()) { // ... ReusablePartOfTheScreen( content = { Button( onClick = { myViewModel.loadData() } ) { Text("Confirm") } } ) } @Composable fun ReusablePartOfTheScreen(content: @Composable () -> Unit) { Column { // ... content() } }
แนะนำสำหรับคุณ
- หมายเหตุ: ข้อความลิงก์จะแสดงเมื่อ JavaScript ปิดอยู่
- องค์ประกอบของธีมใน Compose
- การใช้มุมมองในการเขียน
- Kotlin สำหรับ Jetpack Compose