Composition เป็นกระบวนการหลักใน Jetpack Compose ซึ่งในระหว่างนั้น UI Tree ที่แสดงผลบนหน้าจอจะถูกสร้างขึ้นจากฟังก์ชัน Composable ที่อธิบาย แตกต่างจากระบบ View ของ Android ที่เลย์เอาต์ถูกโหลดจาก XML และแปลงเป็นอ็อบเจกต์ที่ไม่สามารถเปลี่ยนแปลงได้ Composition ทำงานเป็นระบบไดนามิก: ฟังก์ชันทำงาน, สร้างสล็อตในหน่วยความจำ, สร้างลำดับชั้นของโหนด, และเชื่อมโยงกับสถานะ ตาม Google Android Developers, 2026 การทำความเข้าใจ Composition เป็นสิ่งสำคัญสำหรับการเพิ่มประสิทธิภาพของแอปพลิเคชัน Compose
ประเด็นสำคัญ
Composition คือกระบวนการดำเนินการฟังก์ชัน Composable ซึ่งส่งผลให้เกิดการแสดงภายในของอินเทอร์เฟซผู้ใช้เป็น Tree ของโหนด แต่ละโหนดใน Tree นี้สอดคล้องกับคอมโพเนนต์ในตัว (Text, Button, Image) หรือการเรียกฟังก์ชัน Composable ที่ผู้ใช้กำหนด Composition ไม่ได้สร้างอ็อบเจกต์ View ของ Android โดยตรง — มันสร้างคำอธิบายนามธรรมที่ถูกประมวลผลโดยเฟส Layout และ Drawing
คุณสมบัติหลักของ Composition คือ ความสามารถในการเริ่มใหม่ ฟังก์ชัน Composable แต่ละตัวในองค์ประกอบสามารถเริ่มใหม่ได้ตลอดเวลาหากพารามิเตอร์อินพุตหรืออ็อบเจกต์สถานะที่อ่านมีการเปลี่ยนแปลง ระบบไม่ได้เริ่ม Tree ทั้งหมดใหม่ — เฉพาะฟังก์ชันที่ขึ้นอยู่กับข้อมูลที่เปลี่ยนแปลงจริงเท่านั้น
ในทางเทคนิค Composition ถูกจัดการผ่าน Composer — เอ็นจินภายในที่คอมไพเลอร์ Kotlin ฝังลงในทุกฟังก์ชัน Composable Composer เขียนข้อมูลเกี่ยวกับฟังก์ชันที่ถูกเรียก, ด้วยพารามิเตอร์อะไร, และในลำดับใด ลงในสล็อต (กลุ่มตำแหน่ง) ในการเรียกครั้งต่อมา Composer เปรียบเทียบข้อมูลใหม่กับข้อมูลที่เก็บไว้และตัดสินใจว่าจะเริ่มใหม่หรือไม่
กระบวนการสร้าง UI Tree เริ่มต้นด้วยการเรียกเมธอด setContent ภายใน Activity หรือ Fragment เมธอดนี้สร้าง Composition เริ่มต้นและเริ่มดำเนินการฟังก์ชัน Composer ระดับราก จากนั้นฟังก์ชัน Composer ที่ซ้อนกันแต่ละตัวจะเพิ่มโหนดของตนลงใน Tree สร้างลำดับชั้น: Row ประกอบด้วย Text และ Button, Column ประกอบด้วย Image และ Card เป็นต้น
แต่ละโหนด Tree จะได้รับ คีย์ตำแหน่ง เฉพาะตัว ตามตำแหน่งในซอร์สโค้ด คีย์นี้ใช้เพื่อระบุโหนดในการดำเนินการครั้งต่อไป คีย์ตำแหน่งเป็นเหตุผลว่าทำไมลำดับการเรียกฟังก์ชัน Composer จึงไม่ควรขึ้นอยู่กับเงื่อนไข: หากในรันหนึ่งเรียก A -> B และในรันถัดไปเรียก B -> A Compose จะไม่สามารถจับคู่โหนดเก่าและใหม่ได้
@Composable
fun AppScreen() {
Column { // โหนด Column (ตำแหน่ง 1)
HeaderSection() // โหนด HeaderSection (ตำแหน่ง 2)
ContentSection() // โหนด ContentSection (ตำแหน่ง 3)
FooterSection() // โหนด FooterSection (ตำแหน่ง 4)
}
}
@Composable
fun HeaderSection() {
Row { // โหนด Row (ตำแหน่ง 2.1)
Text("หัวเรื่อง") // โหนด Text (ตำแหน่ง 2.2)
Icon(...) // โหนด Icon (ตำแหน่ง 2.3)
}
}
ในตัวอย่างนี้ แต่ละการเรียกได้รับตำแหน่งตามลำดับในโค้ด Column (ตำแหน่ง 1) ประกอบด้วยโหนดลูกสามตัว (ตำแหน่ง 2, 3, 4) HeaderSection เพิ่มโหนดลูกอีกสองตัว (2.1, 2.2, 2.3) หากในการทำซ้ำครั้งถัดไป ContentSection ถูกเรียกก่อน HeaderSection Composer จะไม่สามารถจับคู่โหนดได้อย่างถูกต้อง — ดังนั้นกฎ: ลำดับการเรียกฟังก์ชัน Composer ต้องเสถียร
สถานะใน Composition ถูกจัดการผ่านอ็อบเจกต์ประเภท State<T> เมื่อฟังก์ชัน Composer อ่านค่าจาก State ผ่านคุณสมบัติที่มอบหมาย (by) มันจะลงทะเบียนการพึ่งพาใน State นั้น เมื่อค่าเปลี่ยน ฟังก์ชันทั้งหมดที่อ่าน State นี้จะถูกทำเครื่องหมายให้เริ่มใหม่ในเฟส Composition ถัดไป
กลไกการลงทะเบียนการพึ่งพาเรียกว่า ระบบสแนปชอต ทุกครั้งที่ State เปลี่ยนแปลง สแนปชอตจะบันทึกการเปลี่ยนแปลงทั้งหมดและแจ้ง Composer ว่าฟังก์ชันใดขึ้นอยู่กับ State นั้น สิ่งสำคัญที่ต้องเข้าใจ: การอ่าน State ภายในโค้ดที่ไม่ใช่ Composable (เช่น ใน lambda onClick) ไม่ได้ลงทะเบียนการพึ่งพา — เฉพาะการอ่านภายในฟังก์ชัน Composable หรือใน lambdas ที่ดำเนินการในบริบทของ Composition
ระบบสแนปชอต ทำงานแบบธุรกรรม: การเปลี่ยนแปลง State หลายครั้งภายในเหตุการณ์เดียวจะรวมเป็นธุรกรรมเดียว ป้องกันการทำซ้ำหลายครั้ง ซึ่งสำคัญโดยเฉพาะเมื่อจัดการกับท่าทาง: การเคลื่อนไหวครั้งเดียวเปลี่ยนอ็อบเจกต์ State หลายตัว แต่ Compose ดำเนินการทำซ้ำเพียงครั้งเดียว
@Composable
fun StateExample() {
var text by remember { mutableStateOf("Hello") }
var isVisible by remember { mutableStateOf(true) }
Column {
Text(text) // ลงทะเบียนการพึ่งพาของ text
if (isVisible) { // ลงทะเบียนการพึ่งพาของ isVisible
TextField(value = text, onValueChange = { text = it })
}
Button(onClick = { isVisible = !isVisible }) {
Text(if (isVisible) "ซ่อน" else "แสดง")
}
}
}
การเปลี่ยน text ทำให้เกิดการทำซ้ำเฉพาะ Column, Text และ TextField เท่านั้น Column, Button และเงื่อนไข isVisible ยังคงไม่เปลี่ยนแปลง การแยกตัว ของการทำซ้ำนี้เป็นข้อได้เปรียบหลักของ Compose เหนือระบบที่วาดทั้งหน้าจอใหม่ทั้งหมด ฟังก์ชัน Composable แต่ละตัวติดตามเฉพาะอ็อบเจกต์ State ที่มันอ่านโดยตรง
Composition และ Recomposition เป็นสองโหมดที่แตกต่างกันในการดำเนินการฟังก์ชัน Composable Composition เกิดขึ้นครั้งเดียวเมื่อสร้างหน้าจอ: ระบบดำเนินการฟังก์ชัน Composable ทั้งหมดด้วยค่าเริ่มต้นและสร้าง UI Tree เริ่มต้น Recomposition เกิดขึ้นหลายครั้งเมื่อข้อมูลเปลี่ยนแปลง: ระบบเริ่มเฉพาะฟังก์ชันที่ขึ้นอยู่กับสถานะที่เปลี่ยนแปลงเท่านั้น
โหมด Composition เปิดใช้งานโหนด Tree ทั้งหมด จัดสรรสล็อตสำหรับแต่ละฟังก์ชัน และลงทะเบียนลูกหลานทั้งหมด Recomposition ทำงานแบบเลือก: Compose เปรียบเทียบค่าพารามิเตอร์ใหม่และเก่าของแต่ละฟังก์ชัน และหากไม่เปลี่ยนแปลง — ฟังก์ชันจะไม่ถูกดำเนินการ (การข้าม)
Composition และ Recomposition แตกต่างกันในต้นทุน Composition ครั้งแรก มีราคาแพงกว่าเพราะต้องสร้าง Tree ทั้งหมดและจัดสรรสล็อต Recomposition ถูกกว่า โดยเฉพาะอย่างยิ่งหากฟังก์ชันส่วนใหญ่เสถียร — พารามิเตอร์ของพวกมันถูกเปรียบเทียบด้วย equals และ Compose ข้ามการเรียกของมัน เพื่อประสิทธิภาพสูงสุด คุณควรพยายามให้การทำซ้ำส่วนใหญ่ส่งผลกระทบต่อฟังก์ชันให้น้อยที่สุด
| ลักษณะ | Composition | Recomposition |
|---|---|---|
| เมื่อเกิดขึ้น | ครั้งเดียว เมื่อแสดงครั้งแรก | หลายครั้ง เมื่อข้อมูลเปลี่ยน |
| ขอบเขต | ทั้ง Tree | เฉพาะฟังก์ชันที่เปลี่ยน |
| การเปรียบเทียบพารามิเตอร์ | ไม่ดำเนินการ | ดำเนินการเพื่อการข้าม |
| การสร้างสล็อต | ใช่ สล็อตทั้งหมดถูกสร้าง | เฉพาะโหนดใหม่ |
CompositionLocal เป็นกลไกสำหรับการส่งข้อมูลโดยปริยายผ่าน Tree ของ Composition มันแก้ปัญหาเมื่อพารามิเตอร์ต้องถูกส่งผ่านฟังก์ชัน Composable ที่ซ้อนกันหลายสิบตัวที่ไม่ได้ใช้มันโดยตรง แทนที่จะใช้ลูกโซ่พารามิเตอร์ที่ชัดเจน ข้อมูลถูกกำหนดที่ระดับบนสุดและอ่านในฟังก์ชันที่ซ้อนกันใด ๆ ผ่าน CompositionLocal.current
MaterialTheme เป็นตัวอย่างที่รู้จักกันดีที่สุดของ CompositionLocal คอมโพเนนต์ Compose ทั้งหมดอ่านสี รูปแบบตัวอักษร และรูปร่างผ่าน MaterialTheme.colorScheme, MaterialTheme.typography, MaterialTheme.shapes โดยไม่ได้รับผ่านพารามิเตอร์ นักพัฒนาสามารถสร้าง CompositionLocal ของตนเองสำหรับข้อมูลเช่นผู้ใช้ปัจจุบัน การตั้งค่าการแปลภาษา หรือการกำหนดค่าหน้าจอ
ข้อจำกัดสำคัญ: ไม่ควรใช้ CompositionLocal สำหรับข้อมูลที่เปลี่ยนแปลงบ่อย (ตำแหน่งเลื่อน ข้อความในฟิลด์อินพุต) คอมโพเนนต์ที่อ่าน CompositionLocal จะเริ่มใหม่ทุกครั้งที่ค่าเปลี่ยน ดังนั้นสำหรับข้อมูลไดนามิก ควรใช้พารามิเตอร์ที่ชัดเจนหรือ State CompositionLocal เหมาะที่สุดสำหรับข้อมูลการกำหนดค่าที่เปลี่ยนแปลงน้อยหรือไม่เปลี่ยนแปลงเลย
val LocalUser = compositionLocalOf<User?> { null }
@Composable
fun AppRoot(user: User, content: @Composable () -> Unit) {
CompositionLocalProvider(LocalUser.provides(user)) {
content()
}
}
@Composable
fun UserAvatar() {
val user = LocalUser.current // การอ่านโดยไม่มีพารามิเตอร์ที่ชัดเจน
AsyncImage(model = user?.avatarUrl, contentDescription = "Avatar")
}
CompositionLocalProvider สร้างขอบเขตที่ LocalUser.current ส่งคืนค่าที่ระบุ UserAvatar อ่านผู้ใช้โดยไม่ต้องส่งพารามิเตอร์อย่างชัดเจนผ่านฟังก์ชันตัวกลาง ซึ่งมีค่าโดยเฉพาะในลำดับชั้นที่ลึกซึ่งข้อมูลจำเป็นเพียงไม่กี่โหนดใบ
คำถามที่พบบ่อย
การเปลี่ยน State ระหว่าง Composition จะกำหนดเวลาการทำซ้ำใหม่ ซึ่งจะดำเนินการหลังจากปัจจุบันเสร็จสิ้น ไม่เกิดการวนซ้ำไม่สิ้นสุด: Compose รับประกันว่าการทำซ้ำแต่ละครั้งจะดำเนินการในธุรกรรมแยกต่างหากของระบบสแนปชอต
บนอุปกรณ์สมัยใหม่ Composition ของหน้าจอที่มีฟังก์ชัน Composable 50–100 ตัวใช้เวลา 1–5 มิลลิวินาที Google แนะนำให้อยู่ใน 16 มิลลิวินาทีสำหรับเฟรมที่ 60fps หาก Composition เกินขีดจำกัดนี้ ให้ใช้ LazyColumn หรือแบ่งหน้าจอเป็นฟังก์ชันย่อยๆ
การเริ่ม Composition ด้วยตนเองโดยตรงไม่สามารถทำได้ — มันถูกจัดการโดย Composer โดยอัตโนมัติ อย่างไรก็ตาม คุณสามารถบังคับให้ทำซ้ำได้โดยการเปลี่ยน State หรือเรียก invalidate() ที่ root composable หากคุณสามารถเข้าถึง CompositionContext
ลำดับชั้น View เป็น Tree ที่ไม่สามารถเปลี่ยนแปลงได้ของอ็อบเจกต์ Java ที่ถูกสร้างครั้งเดียว Composition เป็น Tree เสมือนที่ถูกสร้างใหม่ทุกครั้งที่ข้อมูลเปลี่ยน View เก็บสถานะในตัวแปรอินสแตนซ์ Composition — ในสล็อตที่ผูกกับตำแหน่งการเรียกฟังก์ชัน
หากฟังก์ชัน Composable ไม่ถูกเรียกอีกต่อไป (เช่น เงื่อนไข if กลายเป็น false) Composition จะลบโหนดของมันและเรียกใช้การล้างข้อมูล DisposableEffect เมื่อมันปรากฏขึ้นอีกครั้ง (if กลายเป็น true อีกครั้ง) โหนดใหม่จะถูกสร้างขึ้น — โหนดเก่าจะไม่ถูกกู้คืน
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม