ในหน้านี้ คุณจะได้เรียนรู้เกี่ยวกับวงจรของ Composable และวิธีที่ Compose ตัดสินใจว่า Composable ใดต้องมีการจัดองค์ประกอบใหม่
ภาพรวมของวงจร
ดังที่กล่าวไว้ในเอกสารประกอบเกี่ยวกับการจัดการสถานะ การจัดองค์ประกอบจะอธิบาย UI ของแอปและสร้างขึ้นโดยการเรียกใช้ Composable การจัดองค์ประกอบเป็นโครงสร้างแบบต้นไม้ของ Composable ที่อธิบาย UI
เมื่อ Jetpack Compose เรียกใช้ Composable เป็นครั้งแรกในระหว่าง การจัดองค์ประกอบ เริ่มต้น ระบบจะติดตาม Composable ที่คุณเรียกใช้เพื่ออธิบาย UI ในการจัดองค์ประกอบ จากนั้นเมื่อสถานะของแอปเปลี่ยนแปลง Jetpack Compose จะกำหนดเวลา การจัดองค์ประกอบใหม่ การจัดองค์ประกอบใหม่คือเมื่อ Jetpack Compose เรียกใช้ Composable อีกครั้งซึ่งอาจมีการเปลี่ยนแปลงเพื่อตอบสนองต่อการเปลี่ยนแปลงสถานะ แล้วอัปเดตการจัดองค์ประกอบเพื่อแสดงการเปลี่ยนแปลง
การจัดองค์ประกอบจะสร้างขึ้นได้โดยการจัดองค์ประกอบเริ่มต้นเท่านั้น และอัปเดตได้โดยการจัดองค์ประกอบใหม่ วิธีเดียวที่จะแก้ไขการจัดองค์ประกอบได้คือผ่านการจัดองค์ประกอบใหม่
โดยปกติแล้วการจัดองค์ประกอบใหม่จะเกิดขึ้นเมื่อมีการเปลี่ยนแปลงออบเจ็กต์
State<T> Compose จะติดตามการเปลี่ยนแปลงเหล่านี้และเรียกใช้ Composable ทั้งหมดในการจัดองค์ประกอบที่อ่าน State<T> นั้นๆ รวมถึง Composable ใดๆ ที่ Composable เหล่านั้นเรียกใช้ซึ่งข้ามไม่ได้
หากมีการเรียกใช้ Composable หลายครั้ง ระบบจะวางอินสแตนซ์หลายรายการไว้ในการจัดองค์ประกอบ การเรียกใช้แต่ละครั้งจะมีวงจรของตัวเองในการจัดองค์ประกอบ
@Composable fun MyComposable() { Column { Text("Hello") Text("World") } }
MyComposable ในการจัดองค์ประกอบ หากมีการเรียกใช้ Composable หลายครั้ง ระบบจะวางอินสแตนซ์หลายรายการไว้ในการจัดองค์ประกอบ องค์ประกอบที่มีสีต่างกันบ่งบอกว่าเป็นอินสแตนซ์แยกกันโครงสร้างของ Composable ในการจัดองค์ประกอบ
ระบบจะระบุอินสแตนซ์ของ Composable ในการจัดองค์ประกอบตามตำแหน่งที่เรียกใช้ คอมไพเลอร์ Compose จะถือว่าตำแหน่งที่เรียกใช้แต่ละตำแหน่งแตกต่างกัน การเรียกใช้ Composable จากตำแหน่งที่เรียกใช้หลายตำแหน่งจะสร้างอินสแตนซ์ของ Composable หลายรายการในการจัดองค์ประกอบ
หากระหว่างการจัดองค์ประกอบใหม่ Composable เรียกใช้ Composable อื่นๆ ที่แตกต่างจากที่เรียกใช้ระหว่างการจัดองค์ประกอบก่อนหน้า Compose จะระบุ Composable ที่เรียกใช้หรือไม่เรียกใช้ และสำหรับ Composable ที่เรียกใช้ในการจัดองค์ประกอบทั้ง 2 ครั้ง Compose จะหลีกเลี่ยงการจัดองค์ประกอบใหม่หากอินพุตไม่เปลี่ยนแปลง
การรักษาข้อมูลประจำตัวไว้เป็นสิ่งสำคัญในการเชื่อมโยงเอฟเฟกต์ข้างเคียงกับ Composable เพื่อให้เอฟเฟกต์ข้างเคียงทำงานเสร็จสมบูรณ์แทนที่จะเริ่มต้นใหม่ทุกครั้งที่มีการจัดองค์ประกอบใหม่
ลองดูตัวอย่างต่อไปนี้
@Composable fun LoginScreen(showError: Boolean) { if (showError) { LoginError() } LoginInput() // This call site affects where LoginInput is placed in Composition } @Composable fun LoginInput() { /* ... */ } @Composable fun LoginError() { /* ... */ }
ในข้อมูลโค้ดด้านบน LoginScreen จะเรียกใช้ Composable LoginError แบบมีเงื่อนไข และจะเรียกใช้ Composable LoginInput เสมอ การเรียกใช้แต่ละครั้งจะมีตำแหน่งที่เรียกใช้และตำแหน่งในซอร์สโค้ดที่ไม่ซ้ำกัน ซึ่งคอมไพเลอร์จะใช้เพื่อระบุการเรียกใช้แต่ละครั้งโดยไม่ซ้ำกัน
LoginScreen ในการจัดองค์ประกอบเมื่อสถานะเปลี่ยนแปลงและมีการจัดองค์ประกอบใหม่ สีเดียวกันหมายความว่าไม่มีการจัดองค์ประกอบใหม่แม้ว่า LoginInput จะเปลี่ยนจากการเรียกใช้ครั้งแรกเป็นการเรียกใช้ครั้งที่ 2 แต่ระบบจะเก็บอินสแตนซ์ LoginInput ไว้ตลอดการจัดองค์ประกอบใหม่ นอกจากนี้ เนื่องจาก LoginInput ไม่มีพารามิเตอร์ที่เปลี่ยนแปลงตลอดการจัดองค์ประกอบใหม่ Compose จะข้ามการเรียกใช้ LoginInput
เพิ่มข้อมูลเพิ่มเติมเพื่อช่วยในการจัดองค์ประกอบใหม่แบบอัจฉริยะ
การเรียกใช้ Composable หลายครั้งจะเพิ่ม Composable นั้นลงในการจัดองค์ประกอบหลายครั้งด้วย เมื่อเรียกใช้ Composable หลายครั้งจากตำแหน่งที่เรียกใช้เดียวกัน Compose จะไม่มีข้อมูลที่จะระบุการเรียกใช้ Composable นั้นๆ แต่ละครั้งโดยไม่ซ้ำกัน ดังนั้นระบบจึงใช้ลำดับการดำเนินการนอกเหนือจากตำแหน่งที่เรียกใช้เพื่อแยกอินสแตนซ์ออกจากกัน บางครั้งลักษณะการทำงานนี้ก็เพียงพอ แต่ในบางกรณีอาจทำให้เกิดลักษณะการทำงานที่ไม่ต้องการ
@Composable fun MoviesScreen(movies: List<Movie>) { Column { for (movie in movies) { // MovieOverview composables are placed in Composition given its // index position in the for loop MovieOverview(movie) } } }
ในตัวอย่างด้านบน Compose ใช้ลำดับการดำเนินการนอกเหนือจากตำแหน่งที่เรียกใช้เพื่อแยกอินสแตนซ์ออกจากกันในการจัดองค์ประกอบ หากมีการเพิ่ม movie ใหม่ที่ ด้านล่าง ของรายการ Compose จะนำอินสแตนซ์ที่มีอยู่แล้วในการจัดองค์ประกอบกลับมาใช้ซ้ำได้ เนื่องจากตำแหน่งของอินสแตนซ์เหล่านั้นในรายการไม่เปลี่ยนแปลง ดังนั้นอินพุต movie จึงเหมือนกันสำหรับอินสแตนซ์เหล่านั้น
MoviesScreen ในการจัดองค์ประกอบเมื่อมีการเพิ่มองค์ประกอบใหม่ที่ด้านล่างของรายการ Composable MovieOverview ในการจัดองค์ประกอบสามารถนำกลับมาใช้ซ้ำได้ สีเดียวกันใน MovieOverview หมายความว่าไม่มีการจัดองค์ประกอบใหม่อย่างไรก็ตาม หากรายการ movies เปลี่ยนแปลงโดยการเพิ่มรายการที่ ด้านบน หรือ ตรงกลาง ของรายการ การนำรายการออก หรือการจัดลำดับรายการใหม่ การเปลี่ยนแปลงดังกล่าวจะทำให้เกิดการจัดองค์ประกอบใหม่ในการเรียกใช้ MovieOverview ทั้งหมดที่มีพารามิเตอร์อินพุตเปลี่ยนตำแหน่งในรายการ ซึ่งสำคัญอย่างยิ่งหากเช่น MovieOverview ดึงข้อมูลรูปภาพของภาพยนตร์โดยใช้เอฟเฟกต์ข้างเคียง หากมีการจัดองค์ประกอบใหม่ขณะที่เอฟเฟกต์กำลังทำงานอยู่ ระบบจะยกเลิกเอฟเฟกต์นั้นและเริ่มทำงานอีกครั้ง
@Composable fun MovieOverview(movie: Movie) { Column { // Side effect explained later in the docs. If MovieOverview // recomposes, while fetching the image is in progress, // it is cancelled and restarted. val image = loadNetworkImage(movie.url) MovieHeader(image) /* ... */ } }
MoviesScreen ในการจัดองค์ประกอบเมื่อมีการเพิ่มองค์ประกอบใหม่ลงในรายการ Composable MovieOverview ไม่สามารถนำกลับมาใช้ซ้ำได้และเอฟเฟกต์ข้างเคียงทั้งหมดจะเริ่มทำงานอีกครั้ง สีที่ต่างกันใน MovieOverview หมายความว่ามีการจัดองค์ประกอบใหม่ในอุดมคติแล้ว เราต้องการให้คิดว่าข้อมูลประจำตัวของอินสแตนซ์ MovieOverview เชื่อมโยงกับข้อมูลประจำตัวของ movie ที่ส่งผ่านไปยังอินสแตนซ์นั้น หากเราจัดลำดับรายการภาพยนตร์ใหม่ ในอุดมคติแล้วเราจะจัดลำดับอินสแตนซ์ใหม่ในแผนผังการจัดองค์ประกอบในลักษณะเดียวกันแทนที่จะจัดองค์ประกอบใหม่ของ Composable MovieOverview แต่ละรายการด้วยอินสแตนซ์ภาพยนตร์ที่ต่างกัน Compose มีวิธีให้คุณบอกรันไทม์
ว่าต้องการใช้ค่าใดเพื่อระบุส่วนหนึ่งของแผนผัง นั่นคือ
key
Composable
การใส่บล็อกโค้ดไว้ในการเรียกใช้ Composable key พร้อมค่าอย่างน้อย 1 ค่าที่ส่งผ่านเข้าไป ระบบจะรวมค่าเหล่านั้นเพื่อใช้ระบุอินสแตนซ์นั้นในการจัดองค์ประกอบ ค่าของ key ไม่จำเป็นต้องไม่ซ้ำกัน ทั่วโลก แต่ต้องไม่ซ้ำกันเฉพาะในหมู่การเรียกใช้ Composable ที่ตำแหน่งที่เรียกใช้ ดังนั้นในตัวอย่างนี้ movie แต่ละรายการต้องมี
key ที่ไม่ซ้ำกันในหมู่ movies แต่จะใช้ key เดียวกันกับ
Composable อื่นๆ ในส่วนอื่นๆ ของแอปก็ได้
@Composable fun MoviesScreenWithKey(movies: List<Movie>) { Column { for (movie in movies) { key(movie.id) { // Unique ID for this movie MovieOverview(movie) } } } }
ด้วยวิธีข้างต้น แม้ว่าองค์ประกอบในรายการจะเปลี่ยนแปลง Compose ก็จะจดจำการเรียกใช้ MovieOverview แต่ละครั้งและนำกลับมาใช้ซ้ำได้
MoviesScreen ในการจัดองค์ประกอบเมื่อมีการเพิ่มองค์ประกอบใหม่ลงในรายการ เนื่องจาก Composable MovieOverview มีคีย์ที่ไม่ซ้ำกัน Compose จึงจดจำอินสแตนซ์ MovieOverview ที่ไม่เปลี่ยนแปลงและนำกลับมาใช้ซ้ำได้ โดยเอฟเฟกต์ข้างเคียงของอินสแตนซ์เหล่านั้นจะทำงานต่อไปComposable บางรายการมีการรองรับ Composable key ในตัว เช่น LazyColumn อนุญาตให้ระบุ key ที่กำหนดเองใน DSL items
@Composable fun MoviesScreenLazy(movies: List<Movie>) { LazyColumn { items(movies, key = { movie -> movie.id }) { movie -> MovieOverview(movie) } } }
การข้ามหากอินพุตไม่เปลี่ยนแปลง
ระหว่างการจัดองค์ประกอบใหม่ ฟังก์ชัน Composable บางฟังก์ชันที่มีสิทธิ์อาจถูกข้ามการดำเนินการทั้งหมดหากอินพุตไม่เปลี่ยนแปลงจากการจัดองค์ประกอบก่อนหน้า
ฟังก์ชันที่ประกอบกันได้ มีสิทธิ์ถูกข้าม ยกเว้น ในกรณีต่อไปนี้
- ฟังก์ชันมีประเภทการแสดงผลที่ไม่ใช่
Unit - ฟังก์ชันมีคำอธิบายประกอบ
@NonRestartableComposableหรือ@NonSkippableComposable - พารามิเตอร์ที่ต้องระบุมีประเภทที่ไม่เสถียร
มีโหมดคอมไพเลอร์ทดลองใช้ที่ชื่อว่า Strong Skipping, ซึ่งผ่อนปรนข้อกำหนดสุดท้าย
ประเภทที่จะถือว่าเสถียรต้องเป็นไปตามข้อกำหนดต่อไปนี้
- ผลลัพธ์ของ
equalsสำหรับอินสแตนซ์ 2 รายการจะเหมือนกัน ตลอดไป สำหรับอินสแตนซ์ 2 รายการเดียวกัน - หากพร็อพเพอร์ตี้สาธารณะของประเภทเปลี่ยนแปลง ระบบจะแจ้งให้การจัดองค์ประกอบทราบ
- ประเภทพร็อพเพอร์ตี้สาธารณะทั้งหมดก็เสถียรด้วย
มีประเภททั่วไปที่สำคัญบางประเภทที่เป็นไปตามข้อกำหนดนี้ ซึ่งคอมไพเลอร์ Compose จะถือว่าเป็นประเภทเสถียร แม้ว่าจะไม่ได้ทำเครื่องหมายว่าเป็นประเภทเสถียรอย่างชัดเจนโดยใช้คำอธิบายประกอบ @Stable ก็ตาม
- ประเภทค่าดั้งเดิมทั้งหมด เช่น
Boolean,Int,Long,Float,Charเป็นต้น - สตริง
- ฟังก์ชันทุกประเภท (แลมบ์ดา)
ประเภททั้งหมดนี้เป็นไปตามข้อกำหนดของประเภทเสถียรได้เนื่องจากเป็นประเภทที่ไม่เปลี่ยนแปลง เนื่องจากประเภทที่ไม่เปลี่ยนแปลงจะไม่มีการเปลี่ยนแปลง จึงไม่จำเป็นต้องแจ้งให้การจัดองค์ประกอบทราบถึงการเปลี่ยนแปลง ดังนั้นจึงปฏิบัติตามข้อกำหนดนี้ได้ง่ายกว่ามาก
ประเภทที่น่าสังเกตประเภทหนึ่งซึ่งเป็นประเภทเสถียรแต่ เปลี่ยนแปลงได้ คือประเภท MutableState ของ Compose หากค่าอยู่ใน MutableState ออบเจ็กต์สถานะโดยรวมจะ
ถือว่าเป็นประเภทเสถียร เนื่องจาก Compose จะได้รับการแจ้งเตือนการเปลี่ยนแปลงพร็อพเพอร์ตี้
.value ของ State
เมื่อประเภททั้งหมดที่ส่งผ่านเป็นพารามิเตอร์ไปยัง Composable เป็นประเภทเสถียร ระบบจะเปรียบเทียบค่าพารามิเตอร์เพื่อดูความเท่ากันตามตำแหน่งของ Composable ในแผนผัง UI ระบบจะข้ามการจัดองค์ประกอบใหม่หากค่าทั้งหมดไม่เปลี่ยนแปลงจากการเรียกใช้ครั้งก่อน
Compose จะถือว่าประเภทหนึ่งๆ เป็นประเภทเสถียรก็ต่อเมื่อพิสูจน์ได้ ตัวอย่างเช่น โดยทั่วไปอินเทอร์เฟซจะถือว่าเป็นประเภทที่ไม่เสถียร และประเภทที่มีพร็อพเพอร์ตี้สาธารณะที่เปลี่ยนแปลงได้ซึ่งการใช้งานอาจไม่เปลี่ยนแปลงก็ถือว่าเป็นประเภทที่ไม่เสถียรเช่นกัน
หาก Compose ไม่สามารถอนุมานได้ว่าประเภทหนึ่งๆ เป็นประเภทเสถียร แต่คุณต้องการบังคับให้ Compose ถือว่าประเภทนั้นเป็นประเภทเสถียร ให้ทำเครื่องหมายประเภทนั้นด้วยคำอธิบายประกอบ
@Stable
// Marking the type as stable to favor skipping and smart recompositions. @Stable interface UiState<T : Result<T>> { val value: T? val exception: Throwable? val hasError: Boolean get() = exception != null }
ในข้อมูลโค้ดด้านบน เนื่องจาก UiState เป็นอินเทอร์เฟซ โดยปกติแล้ว Compose อาจถือว่าประเภทนี้เป็นประเภทที่ไม่เสถียร การเพิ่มคำอธิบายประกอบ @Stable จะบอกให้ Compose ทราบว่าประเภทนี้เป็นประเภทเสถียร ซึ่งช่วยให้ Compose เลือกการจัดองค์ประกอบใหม่แบบอัจฉริยะได้ นอกจากนี้ยังหมายความว่า Compose จะถือว่าการใช้งานทั้งหมดเป็นประเภทเสถียรหากใช้อินเทอร์เฟซเป็นประเภทพารามิเตอร์
แนะนำสำหรับคุณ
- หมายเหตุ: ข้อความลิงก์จะแสดงเมื่อ JavaScript ปิดอยู่
- สถานะและ Jetpack Compose
- เอฟเฟกต์ข้างเคียงใน Compose
- บันทึกสถานะ UI ใน Compose