ในหน้านี้ คุณจะได้เรียนรู้เกี่ยวกับวงจรของ Composable และวิธีที่ Compose ตัดสินว่า Composable ต้องมีการ Recomposition หรือไม่
ภาพรวมวงจร
ดังที่กล่าวไว้ในเอกสารประกอบเกี่ยวกับการจัดการสถานะ องค์ประกอบ การจัดองค์ประกอบจะอธิบาย UI ของแอปและสร้างขึ้นโดยการเรียกใช้ Composable Composition คือโครงสร้างแบบต้นไม้ของ Composable ที่อธิบาย UI
เมื่อ Jetpack Compose เรียกใช้ Composable เป็นครั้งแรกในระหว่างการ คอมโพสครั้งแรก ระบบจะติดตาม Composable ที่คุณเรียกใช้เพื่ออธิบาย UI ใน Composition จากนั้นเมื่อสถานะของแอปเปลี่ยนแปลง Jetpack Compose จะกำหนดเวลาการจัดองค์ประกอบใหม่ การประกอบใหม่คือเมื่อ Jetpack Compose เรียกใช้ Composable อีกครั้งซึ่งอาจมีการเปลี่ยนแปลงเพื่อตอบสนองต่อการเปลี่ยนแปลงสถานะ แล้วอัปเดต Composition เพื่อแสดงการเปลี่ยนแปลง
โดยการคอมโพสสามารถสร้างได้จากการคอมโพสเริ่มต้นเท่านั้น และอัปเดตได้โดยการคอมโพสใหม่ วิธีเดียวในการแก้ไของค์ประกอบคือการจัดองค์ประกอบใหม่
รูปที่ 1 วงจรของ Composable ใน Composition โดยจะเข้าสู่ Composition, ได้รับการจัดองค์ประกอบใหม่ 0 ครั้งขึ้นไป และออกจาก Composition
โดยปกติแล้ว การประกอบใหม่จะเกิดขึ้นเมื่อมีการเปลี่ยนแปลงออบเจ็กต์ a
State<T>
Compose
จะติดตามสิ่งเหล่านี้และเรียกใช้ Composable ทั้งหมดใน Composition ที่อ่านState<T>
นั้นๆ
และ Composable ใดๆ ที่เรียกใช้ Composable นั้นซึ่งข้ามไม่ได้
หากมีการเรียกใช้ Composable หลายครั้ง ระบบจะวางอินสแตนซ์หลายรายการไว้ใน Composition การเรียกใช้แต่ละครั้งจะมีวงจรของตัวเองในการเรียบเรียง
@Composable fun MyComposable() { Column { Text("Hello") Text("World") } }
รูปที่ 2 การแสดงMyComposable
ในผลงาน หากเรียกใช้
Composable หลายครั้ง ระบบจะวางอินสแตนซ์หลายรายการใน
Composition องค์ประกอบที่มีสีต่างกันแสดงให้เห็นว่าองค์ประกอบนั้นเป็นอินสแตนซ์แยกกัน
โครงสร้างของ Composable ใน Composition
อินสแตนซ์ของ Composable ใน Composition จะระบุโดยตำแหน่งที่เรียกใช้ คอมไพเลอร์ Compose จะถือว่าแต่ละตำแหน่งที่เรียกใช้เป็นตำแหน่งที่แตกต่างกัน การเรียกใช้ Composable จากตำแหน่งการเรียกใช้หลายตำแหน่งจะสร้างอินสแตนซ์หลายรายการของ Composable ใน Composition
หากในระหว่างการจัดองค์ประกอบใหม่ 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
จะเรียกใช้
LoginError
Composable แบบมีเงื่อนไข และจะเรียกใช้ LoginInput
Composable เสมอ การเรียกใช้แต่ละครั้งมีตำแหน่งการเรียกใช้และตำแหน่งแหล่งที่มาที่ไม่ซ้ำกัน ซึ่งคอมไพเลอร์จะใช้เพื่อระบุการเรียกใช้ดังกล่าวแบบไม่ซ้ำกัน
รูปที่ 3 การแสดง LoginScreen
ใน Composition เมื่อสถานะ
เปลี่ยนและเกิดการจัดองค์ประกอบใหม่ สีเดียวกันหมายความว่ายังไม่ได้จัดองค์ประกอบใหม่
แม้ว่า LoginInput
จะเปลี่ยนจากถูกเรียกใช้เป็นอันดับแรกไปเป็นอันดับที่สอง
อินสแตนซ์ LoginInput
จะยังคงอยู่ในการจัดองค์ประกอบใหม่ นอกจากนี้ เนื่องจาก LoginInput
ไม่มีพารามิเตอร์ใดๆ ที่เปลี่ยนแปลงในการ
จัดองค์ประกอบใหม่ Compose จึงจะข้ามการเรียก LoginInput
เพิ่มข้อมูลเพิ่มเติมเพื่อช่วยในการจัดองค์ประกอบใหม่แบบอัจฉริยะ
การเรียก Composable หลายครั้งจะเพิ่มลงใน Composition หลายครั้งด้วย เมื่อเรียกใช้ 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 จะใช้ซ้ำอินสแตนซ์ที่อยู่ใน
Composition ได้เนื่องจากตำแหน่งในรายการไม่เปลี่ยนแปลง และอินพุต movie
จึงเหมือนกันสำหรับอินสแตนซ์เหล่านั้น
รูปที่ 4 การแสดง MoviesScreen
ในการจัดองค์ประกอบเมื่อเพิ่มองค์ประกอบใหม่ที่ด้านล่างของรายการ MovieOverview
ที่ใช้ได้ใน
Composition สามารถนำกลับมาใช้ใหม่ได้ สีเดียวกันใน MovieOverview
หมายความว่า Composable
ยังไม่ได้ทำการ Recompose
อย่างไรก็ตาม หาก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) /* ... */ } }
รูปที่ 5 การแสดง MoviesScreen
ในการจัดองค์ประกอบเมื่อมีการเพิ่มองค์ประกอบใหม่ลงในรายการ
MovieOverview
Composable จะนำกลับมาใช้ใหม่ไม่ได้และ
ผลข้างเคียงทั้งหมดจะรีสตาร์ท สีที่แตกต่างใน MovieOverview
หมายความว่ามีการประกอบMovieOverview
ComposableMovieOverview
อีกครั้ง
ในอุดมคติ เราต้องการพิจารณาตัวตนของMovieOverview
อินสแตนซ์เป็น
ลิงก์กับตัวตนของmovie
ที่ส่งไปยังอินสแตนซ์ หากเราจัดลำดับรายการภาพยนตร์ใหม่ เราควรจัดลำดับอินสแตนซ์ใน
แผนผังการจัดองค์ประกอบใหม่ในลักษณะเดียวกันแทนที่จะจัดองค์ประกอบใหม่สำหรับ MovieOverview
Composable แต่ละรายการด้วยอินสแตนซ์ภาพยนตร์ที่แตกต่างกัน Compose ช่วยให้คุณบอกรันไทม์ได้ว่า
ต้องการใช้ค่าใดเพื่อระบุส่วนที่ต้องการของทรี ซึ่งก็คือ
key
Composables
การครอบบล็อกโค้ดด้วยการเรียกใช้คีย์ที่ใช้ร่วมกันได้โดยมีค่าอย่างน้อย 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
และนำกลับมาใช้ใหม่ได้
รูปที่ 6 การแสดง MoviesScreen
ในการจัดองค์ประกอบเมื่อมีการเพิ่มองค์ประกอบใหม่ลงในรายการ
เนื่องจาก MovieOverview
Composable มีคีย์ที่ไม่ซ้ำกัน
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) } } }
ข้ามหากอินพุตไม่มีการเปลี่ยนแปลง
ในระหว่างการจัดองค์ประกอบใหม่ ฟังก์ชันที่สามารถจัดองค์ประกอบได้บางฟังก์ชันอาจข้ามการดำเนินการทั้งหมดได้หากอินพุตไม่เปลี่ยนแปลงจากการจัดองค์ประกอบก่อนหน้า
ฟังก์ชันที่ประกอบกันได้จะมีสิทธิ์ข้ามเว้นแต่
- ฟังก์ชันมีประเภทการคืนค่าที่ไม่ใช่
Unit
- ฟังก์ชันมีคำอธิบายประกอบด้วย
@NonRestartableComposable
หรือ@NonSkippableComposable
- พารามิเตอร์ที่ต้องระบุมีประเภทที่ไม่เสถียร
มีโหมดคอมไพเลอร์เวอร์ชันทดลอง Strong Skipping ซึ่งผ่อนปรนข้อกำหนดสุดท้าย
ประเภทต้องเป็นไปตามสัญญาต่อไปนี้จึงจะถือว่าเสถียร
- ผลลัพธ์ของ
equals
สำหรับอินสแตนซ์ 2 รายการจะเหมือนกันเสมอสำหรับ อินสแตนซ์ 2 รายการเดียวกัน - หากมีการเปลี่ยนแปลงพร็อพเพอร์ตี้สาธารณะของประเภท ระบบจะแจ้งให้ Composition ทราบ
- นอกจากนี้ พร็อพเพอร์ตี้สาธารณะทุกประเภทก็มีความเสถียรเช่นกัน
มีประเภททั่วไปที่สำคัญบางประเภทที่อยู่ในสัญญาฉบับนี้ ซึ่งคอมไพเลอร์ Compose จะถือว่ามีเสถียรภาพ แม้ว่าจะไม่ได้ทำเครื่องหมายอย่างชัดเจนว่ามีเสถียรภาพโดยใช้คำอธิบายประกอบ @Stable
ก็ตาม
- ประเภทค่าดั้งเดิมทั้งหมด:
Boolean
,Int
,Long
,Float
,Char
ฯลฯ - เครื่องสาย
- ฟังก์ชันทุกประเภท (Lambda)
ประเภททั้งหมดนี้สามารถปฏิบัติตามสัญญาของความเสถียรได้เนื่องจากเป็น แบบคงที่ เนื่องจากประเภทที่เปลี่ยนแปลงไม่ได้จะไม่เปลี่ยนแปลง จึงไม่จำเป็นต้องแจ้งให้ทราบถึงการเปลี่ยนแปลง ดังนั้นจึงปฏิบัติตามสัญญาฉบับนี้ได้ง่ายกว่ามาก
ประเภทที่น่าสนใจประเภทหนึ่งซึ่งมีความเสถียรแต่เปลี่ยนแปลงได้คือ 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
- ผลข้างเคียงในฟีเจอร์เขียน
- บันทึกสถานะ UI ใน Compose