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