ใน Jetpack Compose นักพัฒนาสามารถสร้าง Composable ได้ตามใจชอบว่าจะให้ UI มีลักษณอย่างใด รวมถึงจัดการกับ State และ Event ที่อยู่ใน Composable ด้วย และหนึ่งในเทคนิคสำคัญที่จะทำให้นักพัฒนาสามารถสร้าง Composable ได้อย่างมีประสิทธิภาพก็คือการทำ State Hoisting นั่นเอง

บทความในชุดเดียวกัน

และการทำ 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,
       )
       /* ... */
    )
    /* ... */
}

แหล่งข้อมูลอ้างอิง