Recomposition เป็นกลไกของ Jetpack Compose ที่สร้างส่วนต่าง ๆ ของส่วนติดต่อผู้ใช้ขึ้นใหม่โดยอัตโนมัติเมื่อข้อมูลเปลี่ยนแปลง โดยไม่ต้องอัปเดต View ด้วยตนเอง เมื่อตัวแปรสถานะที่ฟังก์ชัน Composable ขึ้นอยู่กับเปลี่ยนค่า Compose จะเริ่มต้นฟังก์ชันนั้นใหม่เท่านั้น โดยปล่อยให้ส่วนที่เหลือของต้นไม้ UI ไม่ถูกแตะต้อง ตาม Google Android Developers, 2026 การเข้าใจ Recomposition อย่างถูกต้องช่วยลดการวาดซ้ำที่ไม่จำเป็นลง 40–60%
ประเด็นสำคัญ
Recomposition คือการดำเนินการซ้ำของฟังก์ชัน Composable ที่เคยเข้าร่วมใน Composition แล้ว ด้วยค่าพารามิเตอร์หรือสถานะใหม่ เป้าหมายหลักของการประกอบใหม่คือการซิงโครไนซ์ต้นไม้ UI กับข้อมูลปัจจุบันโดยไม่ต้องสร้างส่วนติดต่อทั้งหมดขึ้นใหม่ตั้งแต่ต้น แตกต่างจาก Composition ที่เกิดขึ้นครั้งเดียว Recomposition สามารถถูกกระตุ้นได้หลายร้อยครั้งตลอดอายุของหน้าจอ
Recomposition ทำงานบนหลักการของ smart invalidation: Compose ติดตามว่าฟังก์ชัน Composable แต่ละตัวอ่านวัตถุ State ใด และทำเครื่องหมายเพื่อเริ่มต้นใหม่เฉพาะฟังก์ชันที่การพึ่งพาเปลี่ยนแปลงไป ซึ่งทำได้ผ่านระบบ snapshot ที่บันทึกการดำเนินการอ่าน State ทั้งหมดระหว่างการดำเนินการ และ Composer ที่จับคู่การพึ่งพาเหล่านี้กับฟังก์ชันเฉพาะ
สิ่งสำคัญที่ต้องเข้าใจ: การประกอบใหม่ไม่ได้หมายถึงการวาดหน้าจอใหม่ทันที Compose ทำงานในสามเฟส: Composition (สร้างคำอธิบาย UI), Layout (คำนวณขนาดและตำแหน่ง), และ Drawing (เรนเดอร์บนผืนผ้าใบ) หากหลังจากการประกอบใหม่ขนาดและตำแหน่งขององค์ประกอบไม่เปลี่ยนแปลง เฟส Layout สามารถข้ามได้ หากลักษณะที่ปรากฏไม่เปลี่ยนแปลง — Drawing จะถูกข้าม สถาปัตยกรรมสามเฟสนี้รับประกันต้นทุนขั้นต่ำสำหรับการอัปเดต UI แต่ละครั้ง
มีตัวกระตุ้นหลักสามประการของ การประกอบใหม่ ประการแรกคือการเปลี่ยนแปลงของวัตถุ State ที่อ่านภายในเนื้อความของฟังก์ชัน Composable เมื่อ mutableStateOf หรือ derivedStateOf เปลี่ยนค่า ฟังก์ชันทั้งหมดที่ลงทะเบียนการอ่าน State นี้ใน composition ก่อนหน้าจะถูกทำเครื่องหมายเพื่อเริ่มต้นใหม่
ตัวกระตุ้นที่สองคือ การเปลี่ยนแปลงพารามิเตอร์ ของฟังก์ชัน Composable เมื่อถูกเรียกจากฟังก์ชันหลัก หากฟังก์ชันหลักส่งค่าใหม่ (เช่น ข้อความหรือตัวเลขเปลี่ยนแปลง) ฟังก์ชันลูกจะถูกเริ่มต้นใหม่ แม้ว่าจะไม่ได้อ่าน State ภายในก็ตาม Compose เปรียบเทียบค่าพารามิเตอร์ใหม่และเก่าผ่าน equals และหากเท่ากัน — ฟังก์ชันสามารถข้ามได้
ตัวกระตุ้นที่สามคือ การเปลี่ยนแปลง CompositionLocal ผ่าน CompositionLocalProvider ฟังก์ชันทั้งหมดที่อ่าน CompositionLocal ผ่าน .current จะถูกเริ่มต้นใหม่เมื่อผู้ให้บริการเปลี่ยนแปลง กลไกนี้ถูกใช้โดย MaterialTheme: การเปลี่ยนธีม (สว่าง/มืด) ทำให้เกิดการประกอบใหม่ของส่วนประกอบทั้งหมดที่อ่าน MaterialTheme.colorScheme
@Composable
fun RecompositionDemo() {
var counter by remember { mutableStateOf(0) }
var text by remember { mutableStateOf("Hello") }
Column {
Text("Counter: $counter") // recomposition when counter changes
Text("Message: $text") // recomposition when text changes
Button(onClick = { counter++ }) {
Text("+1")
}
Button(onClick = { text = "World" }) {
Text("Change Text")
}
}
}
การคลิกปุ่ม +1 จะเปลี่ยน counter ทำให้เกิดการประกอบใหม่เฉพาะบรรทัด Text แรกและ Column เท่านั้น บรรทัด Text ที่สองที่แสดง text จะไม่เริ่มต้นใหม่ การแยกนี้ เป็นผลลัพธ์ของระบบ snapshot: ฟังก์ชัน Composable แต่ละตัวรู้เฉพาะวัตถุ State ที่มันได้อ่าน
การปรับแต่ง การประกอบใหม่ เริ่มต้นด้วยการเลือกโครงสร้างข้อมูลที่เหมาะสม ใช้คอลเล็กชันที่ไม่เปลี่ยนสภาพ (listOf, mapOf) แทนคอลเล็กชันที่เปลี่ยนสภาพได้ (mutableListOf) Compose เปรียบเทียบพารามิเตอร์ผ่าน equals และหากคอลเล็กชันเปลี่ยนแปลงแต่ equals คืนค่า true — ฟังก์ชันจะไม่เริ่มต้นใหม่ สำหรับคอลเล็กชันที่เปลี่ยนสภาพได้ ให้ใช้ SnapshotStateList ซึ่งติดตามการเปลี่ยนแปลงอย่างถูกต้องในระดับองค์ประกอบ
เทคนิคที่สองคือ การแยกส่วนที่เสถียร ของ UI ออกเป็นฟังก์ชัน Composable แยกต่างหาก หากส่วนหนึ่งของหน้าจอไม่ขึ้นอยู่กับสถานะที่เปลี่ยนแปลงบ่อย ให้แยกออกเป็นฟังก์ชันแยกต่างหากพร้อมพารามิเตอร์ เมื่อการประกอบใหม่เกิดขึ้น ฟังก์ชันที่เสถียรจะได้รับพารามิเตอร์เดียวกัน Compose เปรียบเทียบและข้ามการดำเนินการ ซึ่งมีประสิทธิภาพมากกว่าการเริ่มต้นส่วนนั้นใหม่ซึ่งเป็นส่วนหนึ่งของฟังก์ชันใหญ่ที่พารามิเตอร์บางตัวเปลี่ยนแปลง
เทคนิคที่สามคือ คีย์ใน LazyColumn ระบุ key สำหรับ item ใน LazyColumn, LazyGrid และคอนเทนเนอร์ขี้เกียจอื่น ๆ เสมอ คีย์ช่วยให้ Compose ระบุองค์ประกอบเมื่อรายการเปลี่ยนแปลง: การเพิ่ม การลบ หรือการเรียงลำดับใหม่ หากไม่มีคีย์ Compose จะเริ่มต้นรายการทั้งหมดในรายการใหม่เมื่อมีการเปลี่ยนแปลงใด ๆ ซึ่งในรายการขนาดใหญ่ทำให้ประสิทธิภาพลดลงอย่างเห็นได้ชัด
// Optimized structure: stable parts extracted separately
@Composable
fun OptimizedScreen(items: List<Item>) {
Column {
Header() // does not depend on items — no recomposition
Spacer(modifier = Modifier.height(8.dp))
LazyColumn {
items(items, key = { it.id }) { item ->
ItemRow(item = item) // recomposition only for changed items
}
}
}
}
@Composable
fun Header() {
Text("Item list", style = MaterialTheme.typography.headlineMedium)
}
@Composable
fun ItemRow(item: Item) {
Text(item.title)
}
Skipping เป็นกลไกที่ Compose ข้ามการดำเนินการของฟังก์ชัน Composable หากพารามิเตอร์ทั้งหมดไม่เปลี่ยนแปลง เพื่อให้การข้ามทำงานอย่างถูกต้อง ชนิดของพารามิเตอร์ต้องเสถียร (stable) คอมไพเลอร์ Kotlin ทำเครื่องหมายเป็นเสถียร: ชนิดดั้งเดิม (Int, Float, Boolean), String, ฟังก์ชัน lambda และคลาสที่ฟิลด์ทั้งหมดเสถียรและเป็น val
Stability คือคำอธิบายประกอบ @Stable หรือ @Immutable ที่สามารถเพิ่มไปยังคลาสข้อมูลที่กำหนดเอง หากคลาสมีฟิลด์ที่เปลี่ยนสภาพได้ (var) คอมไพเลอร์จะถือว่าคลาสนั้นไม่เสถียร และ Compose จะไม่สามารถข้ามฟังก์ชันที่มีพารามิเตอร์ดังกล่าวได้ สำหรับคลาสที่มี var ให้ใช้ @Stable หากคุณรับประกันว่าการแจ้งเตือนการเปลี่ยนแปลงจะถูกส่งผ่านระบบ snapshot
คุณสามารถตรวจสอบความเสถียรได้ผ่าน แฟล็กคอมไพเลอร์ -P "plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=. /reports" มันสร้างรายงานพร้อมรายการฟังก์ชัน Composable ทั้งหมดและพารามิเตอร์ของพวกเขาพร้อมระบุความเสถียร หากพารามิเตอร์ไม่เสถียร — การข้ามเป็นไปไม่ได้สำหรับฟังก์ชันนั้น และมันจะเริ่มต้นใหม่ทุกครั้งที่มีการประกอบใหม่ของฟังก์ชันหลัก
| ชนิด | ความเสถียร | Skipping |
|---|---|---|
| Int, Float, Boolean | เสถียร | ใช่ |
| String | เสถียร | ใช่ |
| Lambda | เสถียร | ใช่ |
| data class ที่มีฟิลด์ val | เสถียร | ใช่ |
| data class ที่มีฟิลด์ var | ไม่เสถียร | ไม่ |
| List<String> | ไม่เสถียร | ไม่ |
หมายเหตุ: List<String> ถือว่าไม่เสถียรเนื่องจากเป็นอินเทอร์เฟซ ไม่ใช่การนำไปใช้จริง ใช้ immutableListOf() จากไลบรารี Kotlin Collections Immutable หรือห่อรายการในคลาส @Stable Lambda เสถียรเสมอเพราะ equals ของมันเปรียบเทียบเฉพาะการอ้างอิง และเมื่อสร้าง lambda ใหม่ที่ตำแหน่งเรียก ฟังก์ชันหลักก็เริ่มต้นใหม่เช่นกัน
สำหรับการตรวจสอบ การประกอบใหม่ Android Studio มี Layout Inspector พร้อมโหมด Compose Recomposition Counts ในโหมดนี้ ฟังก์ชัน Composable แต่ละตัวจะแสดงจำนวนการประกอบใหม่และสาเหตุของการเริ่มต้นใหม่ ซึ่งช่วยให้คุณค้นหาฟังก์ชันที่ประกอบใหม่บ่อยเกินไปได้อย่างรวดเร็วและระบุสาเหตุหลัก — พารามิเตอร์ไม่เสถียรหรือการพึ่งพา State ที่ไม่จำเป็น
เครื่องมือเพิ่มเติม: Compose Metrics (การรวบรวมสถิติผ่านการทดสอบ instrumentation) และ Recomposition Timer (การวัดเวลาดำเนินการของแต่ละฟังก์ชัน) Google แนะนำให้เปิดใช้งานเครื่องมือเหล่านี้ในขั้นตอนการทำโปรไฟล์และปิดใช้งานในบิลด์ที่เผยแพร่ เนื่องจากเพิ่มโอเวอร์เฮดสูงถึง 20% ต่อการประกอบใหม่
เมื่อวิเคราะห์การประกอบใหม่ ให้ค้นหารูปแบบ การประกอบใหม่ที่ไม่จำเป็น: ฟังก์ชันเริ่มต้นใหม่แม้ว่า UI เอาต์พุตไม่ควรเปลี่ยนแปลง สาเหตุทั่วไปคือการใช้ lambda โดยไม่มี remember ซึ่งสร้างวัตถุ lambda ใหม่ทุกครั้งและ Compose ถือว่าพารามิเตอร์เปลี่ยนแปลง วิธีแก้ไข: ห่อ lambda ใน remember { } ด้วย captures ที่คงที่
// Bad: new lambda on every parent recomposition
@Composable
fun Parent() {
Child(onClick = { doSomething() }) // new lambda every time
}
// Good: remember stabilizes the lambda
@Composable
fun Parent() {
val onClick = remember { { doSomething() } }
Child(onClick = onClick) // same reference
}
คำถามที่พบบ่อย
ไม่ การประกอบใหม่ เป็นเพียงเฟส Composition เท่านั้น หลังจากนั้น Layout และ Drawing จะดำเนินการ หากหลังจากการประกอบใหม่ขนาดและตำแหน่งขององค์ประกอบไม่เปลี่ยนแปลง Layout และ Drawing สามารถข้ามได้ทั้งหมด ประหยัดทรัพยากร GPU
ในระหว่างแอนิเมชัน การประกอบใหม่ สามารถทำงานได้สูงสุด 120 ครั้งต่อวินาที (120fps) สำหรับการโต้ตอบปกติ — 10–60 ครั้งต่อวินาที สิ่งสำคัญคือการประกอบใหม่แต่ละครั้งต้องอยู่ในงบประมาณเฟรม (8–16 ms) มิฉะนั้นแอปพลิเคชันจะกระตุก
สาเหตุคือ การเปลี่ยนแปลงพารามิเตอร์ จากฟังก์ชันหลัก ฟังก์ชันหลักเริ่มต้นใหม่ (ด้วยเหตุผลของตัวเอง) และส่งค่าใหม่ เพื่อหลีกเลี่ยงนี้ ให้ตรวจสอบความเสถียรของพารามิเตอร์และใช้ remember เพื่อทำให้ lambda และค่าที่คำนวณมีความเสถียร
ไม่มีวิธีการปิดโดยตรง แต่มี การบังคับข้าม ผ่าน readInComposition — State ถูกอ่านภายนอกเนื้อความของฟังก์ชัน ซึ่งไม่ลงทะเบียนการพึ่งพา ใช้ด้วยความระมัดระวัง: ฟังก์ชันจะไม่ตอบสนองต่อการเปลี่ยนแปลง ซึ่งอาจนำไปสู่ UI ที่ล้าสมัย
Composition แพงกว่าเพราะสร้างสล็อตและโหนดต้นไม้ทั้งหมดตั้งแต่ต้น Recomposition ใช้สล็อตที่มีอยู่ซ้ำและอัปเดตเฉพาะค่าเท่านั้น ในทางปฏิบัติ Composition ของหน้าจอใช้เวลา 2–10 ms ในขณะที่การประกอบใหม่ขององค์ประกอบเดียวใช้เวลา 0.1–1 ms
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม