ใน Jetpack Compose นักพัฒนาสามารถสร้าง Composable ได้ตามใจชอบว่าจะให้ UI มีลักษณอย่างใด รวมถึงจัดการกับ State และ Event ที่อยู่ใน Composable ด้วย และหนึ่งในเทคนิคสำคัญที่จะทำให้นักพัฒนาสามารถสร้าง Composable ได้อย่างมีประสิทธิภาพก็คือการทำ State Hoisting นั่นเอง
บทความในชุดเดียวกัน
- Stateless & Stateful Composable
- State Hoisting [Now Reading]
- Slot API
- Shape
- UI Preview
- Composition Local
- Adoption Strategy
และการทำ State Hoisting ก็จะทำให้ Composable นั้น ๆ กลายเป็น Stateless Composable ไปโดยปริยาย ซึ่งจะมีข้อดีดังนี้
- Single Source of Truth - คาดเดาการทำงานที่จะเกิดขึ้นได้ง่าย ลดบั๊กที่เกิดจากการจัดการ State ที่ไม่ครอบคลุม
- Encapsulated - มีการทำงานที่จบในตัว ไม่สามารถถูกเปลี่ยนแปลงได้ และทำงานตาม State ที่ส่งเข้ามาเท่านั้น
- Shareable - สามารถใช้ State ร่วมกับ Composable ตัวอื่นได้
- Interceptable - สามารถแก้ไขหรือหรือจัดการกับ State ก่อนที่จะส่งเข้ามาใน Composable ได้
- Decoupled - State จะเก็บไว้ที่ไหนก็ได้ ทำให้ Composable ไม่ยึดติดกับ State Holder ใด ๆ จะเก็บไว้ใน Stateful Composable ก็ได้ หรือจะเก็บไว้ใน ViewModel ก็ได้เช่นกัน
โดยการทำ State Hoisting นั้นมีหลักการง่าย ๆ ก็คือการย้าย State และ Event ที่อยู่ภายใน Composable ให้กลายเป็น Argument ที่ส่งเข้ามาใน Composable Function นั้น ๆ แทน
สมมติว่าเจ้าของบล็อกมี Composable แบบนี้
@Composable
fun Topic() {
val icon: Int = R.drawable.ic_title
val title: String = "Title"
Row(
modifier = Modifier.clickable(
onClick = {
// Do something
}
)
) {
Icon(painter = painterResource(icon), /* ... */)
Text(text = title, /* ... */)
}
}
จะเห็นว่า State และ Event ทั้งหมดอยู่ใน Composable ที่ชื่อว่า Topic
ทั้งหมด ทำให้การนำไปใช้งานทำได้ค่อนข้างจำกัด
เมื่อทำ State Hoisting ก็จะกลายเป็นแบบนี้แทน
@Composable
fun Topic(
@ResId icon: Int,
title: String,
onTopicClicked: () -> Unit,
) {
Row(
modifier = Modifier.clickable(
onClick = onTopicClicked
)
) {
Icon(painter = painterResource(icon), /* ... */)
Text(text = title, /* ... */)
}
}
นอกจากนี้นักพัฒนายังสามารถรับ Modifier เข้ามาแบบเดียวกับ State อื่น ๆ เพื่อส่งให้กับ Row
ก่อนที่จะเพิ่มคำสั่ง clickable
เพิ่มเข้าไปใน Modifier ก็ได้เช่นกัน
@Composable
fun Topic(
modifier: Modifier = Modifier,
@ResId icon: Int,
title: String,
onTopicClicked: () -> Unit,
) {
Row(
modifier = modifier.clickable(
onClick = onTopicClicked
)
) {
Icon(painter = painterResource(icon), /* ... */)
Text(text = title, /* ... */)
}
}
ด้วยวิธีนี้จึงทำให้ Topic
กลายเป็น Stateless Composable ที่สามารถนำไปใช้งานได้ง่าย เอาไปใช้ซ้ำก็ได้
@Composable
fun MainScreen() {
Topic(
icon = R.drawable.ic_title,
title = "Title",
onTopicClicked = { /* Do something */ }
)
/* ... */
}
อีกทั้งยังเปลี่ยนรูปแบบในการแสดงผลผ่าน Modifier ได้ประมาณนึงด้วย เช่น กำหนดสีพื้นหลังที่แตกต่างกันไป
@Composable
fun MainScreen() {
Topic(
modifier = Modifier.background(
color = Color.White,
shape = RectangleShape,
)
/* ... */
)
/* ... */
}