วงจรของ Composable

ในหน้านี้ คุณจะได้เรียนรู้เกี่ยวกับวงจรของ Composable และ วิธีที่ Compose ใช้ในการตัดสินว่าจะต้องจัดองค์ประกอบใหม่สำหรับ Composable หรือไม่

ภาพรวมของวงจร

ตามที่ระบุไว้ในเอกสารประกอบของการจัดการสถานะ การเรียบเรียงอธิบาย UI ของแอปและสร้างขึ้นโดยการเรียกใช้ Composable การเรียบเรียงเป็นโครงสร้างแบบต้นไม้ของ Composable ที่อธิบาย UI ของคุณ

เมื่อ Jetpack Compose เรียกใช้ Composable เป็นครั้งแรกระหว่างครั้งแรก ของคอมโพเนนต์ ระบบจะติดตาม Composable ที่คุณเรียกใช้เพื่ออธิบาย UI ในการเรียบเรียง จากนั้นเมื่อสถานะของแอปเปลี่ยนไป Jetpack การเขียนจะกำหนดเวลาการจัดองค์ประกอบใหม่ การจัดองค์ประกอบใหม่คือการใช้ Jetpack Compose เรียกใช้ Composable อีกครั้งอาจมีการเปลี่ยนแปลงเพื่อตอบสนองต่อการเปลี่ยนแปลงสถานะ จากนั้นจึงอัปเดตการเรียบเรียงเพื่อแสดงการเปลี่ยนแปลง

การเรียบเรียงเพลงจะสร้างโดยการเรียบเรียงเพลงเริ่มต้นและอัปเดตโดย การจัดองค์ประกอบใหม่ วิธีเดียวในการแก้ไขการเรียบเรียงคือผ่านการจัดองค์ประกอบใหม่

แผนภาพแสดงวงจรของ Composable

รูปที่ 1 วงจรของ Composable ในการเรียบเรียง จะเข้าสู่ การเรียบเรียง ได้รับการจัดองค์ประกอบใหม่ตั้งแต่ 0 ครั้งขึ้นไป แล้วออกจากการเรียบเรียง

โดยทั่วไปการจัดองค์ประกอบใหม่จะทริกเกอร์โดยการเปลี่ยนแปลง State<T> เขียน ติดตามสิ่งเหล่านี้และเรียกใช้ Composable ทั้งหมดในการเรียบเรียงที่อ่านค่า State<T> โดยเฉพาะ และ Composable ที่เรียกใช้ซึ่งไม่สามารถ ข้าม

หากมีการเรียกใช้ Composable หลายครั้ง จะมีหลายอินสแตนซ์อยู่ในพารามิเตอร์ การเรียบเรียง การเรียกแต่ละรายการมีวงจรชีวิตในการเรียบเรียงของตัวเอง

@Composable
fun MyComposable() {
    Column {
        Text("Hello")
        Text("World")
    }
}

แผนภาพแสดงการจัดเรียงองค์ประกอบตามลำดับชั้นในข้อมูลโค้ดก่อนหน้านี้

รูปที่ 2 การแทนค่า MyComposable ในการเรียบเรียง หากมี Composable มีการเรียกใช้หลายครั้ง โดยจะมีอินสแตนซ์หลายรายการวางอยู่ใน การเรียบเรียง องค์ประกอบที่มีสีต่างกันจะบ่งชี้ว่าเป็น แยกอินสแตนซ์

กายวิภาคของ Composable ในการเรียบเรียง

อินสแตนซ์ของ Composable ในการเรียบเรียงจะระบุโดยไซต์การเรียกใช้ คอมไพเลอร์ Compose จะถือว่าเว็บไซต์การเรียกแต่ละแห่งแตกต่างกัน กำลังเรียก Composable จากเว็บไซต์การเรียกใช้หลายแห่งจะสร้างอินสแตนซ์ Composable หลายอินสแตนซ์ใน การเรียบเรียง

หาก Composable เรียก Composable ระหว่างการจัดองค์ประกอบใหม่แตกต่างจากที่เรียก Composable ในระหว่างการเขียนก่อนหน้านี้ Compose จะ ระบุว่า Composable ใดได้รับการระบุ เรียกใช้หรือไม่เรียกใช้ และสำหรับ Composable ที่มีการเรียกใช้ทั้งใน การแต่งเพลง 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 เสมอ ชิ้น มีไซต์การเรียกใช้และตำแหน่งแหล่งที่มาที่ไม่ซ้ำกัน ซึ่งคอมไพเลอร์จะใช้เพื่อ เป็นเอกลักษณ์ของตัวเองได้

แผนภาพที่แสดงให้เห็นว่าโค้ดก่อนหน้ามีการจัดองค์ประกอบใหม่อย่างไร หากแฟล็ก showError เปลี่ยนเป็น &quot;จริง&quot; เพิ่ม ComputeError แล้ว แต่ Composable อื่นจะไม่เขียนใหม่

รูปที่ 3 การแสดง LoginScreen ในการเรียบเรียงเมื่อสถานะ และการจัดองค์ประกอบใหม่จะเกิดขึ้น สีเดียวกันหมายความว่ายังไม่ได้ปรับองค์ประกอบใหม่

แม้ว่า LoginInput จากชื่อที่ 1 เป็นชื่อที่ 2 แล้วก็ตาม ระบบจะเก็บรักษาอินสแตนซ์ LoginInput ในการจัดองค์ประกอบใหม่ นอกจากนี้ เนื่องจาก LoginInput ไม่มีพารามิเตอร์ที่มีการเปลี่ยนแปลงใน การจัดองค์ประกอบใหม่ การเขียนจะข้ามการเรียกไปยัง LoginInput

ใส่ข้อมูลเพิ่มเติมเพื่อช่วยจัดองค์ประกอบใหม่อย่างชาญฉลาด

การเรียก 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 สำหรับอินสแตนซ์เหล่านั้นจะเหมือนกัน

แผนภาพแสดงวิธีการเรียบเรียงโค้ดที่อยู่ก่อนหน้าใหม่หากมีการเพิ่มองค์ประกอบใหม่ที่ด้านล่างของรายการ รายการอื่นๆ ในรายการไม่มีการเปลี่ยนแปลงตำแหน่ง และไม่ได้เขียนใหม่

รูปที่ 4 การแสดง MoviesScreen ในการเรียบเรียงเมื่อ ไว้ที่ด้านล่างของรายการ Composable MovieOverview รายการใน นำการเรียบเรียงมาใช้ซ้ำได้ สีเดียวกันใน MovieOverview หมายถึง Composable ยังไม่ได้เขียนใหม่

แต่ถ้ารายการ 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 ในการเรียบเรียงเมื่อ ลงในรายการ Composable MovieOverview รายการไม่สามารถนำมาใช้ซ้ำและ ผลข้างเคียงทั้งหมดจะเริ่มต้นใหม่ สีอื่นใน MovieOverview หมายถึง มีการคอมไพล์ Composable ใหม่แล้ว

ตามหลักการ เราต้องการให้คิดว่าเอกลักษณ์ของอินสแตนซ์ MovieOverview คือ ลิงก์กับข้อมูลประจำตัวของ movie ที่ส่งไปยัง หากเราเรียงลำดับ ของภาพยนตร์ หากเป็นไปได้ เราควรจัดเรียงอินสแตนซ์ใน แผนผังการเรียบเรียงแทนที่จะเขียน MovieOverview แต่ละรายการที่เขียนได้ด้วย ภาพยนตร์ตัวอย่างอื่นๆ การเขียนช่วยให้คุณบอกรันไทม์ได้ ค่าที่คุณต้องการใช้เพื่อระบุส่วนที่กำหนดของต้นไม้ ซึ่งได้แก่ key Composable

รวมกลุ่มโค้ดด้วยการเรียกไปยังคีย์ที่ประกอบได้ด้วย 1 หรือหลายรายการ ค่าที่ส่งไปแล้ว ระบบจะรวมค่าเหล่านั้นเข้าด้วยกันเพื่อใช้ระบุ ในการเขียน ค่าของ key ไม่จำเป็นต้องเป็น ทั่วโลก ต้องไม่ซ้ำกัน เฉพาะในระหว่างการเรียกใช้ Composable ที่ไซต์การโทร ดังนั้นในตัวอย่างนี้ movie แต่ละรายการต้องมีแอตทริบิวต์ key ที่ไม่ซ้ำกันระหว่าง movies ก็ไม่เป็นไรหากแชร์ key นั้นกับ อื่นๆ ที่เขียนได้ด้วย Compose ในที่อื่นๆ ในแอป

@Composable
fun MoviesScreenWithKey(movies: List<Movie>) {
    Column {
        for (movie in movies) {
            key(movie.id) { // Unique ID for this movie
                MovieOverview(movie)
            }
        }
    }
}

ด้วยเงื่อนไขข้างต้น แม้ว่าองค์ประกอบในรายการจะมีการเปลี่ยนแปลง แต่ Compose จะจดจำได้ การโทรแต่ละครั้งไปยัง MovieOverview และสามารถใช้ซ้ำได้

แผนภาพแสดงวิธีการเรียบเรียงโค้ดที่อยู่ก่อนหน้าใหม่หากมีการเพิ่มองค์ประกอบใหม่ไว้ที่ด้านบนสุดของรายการ เนื่องจากรายการจะถูกระบุโดยคีย์ Compose จึงไม่สามารถเขียนใหม่แม้ว่าตำแหน่งจะเปลี่ยนแปลงไปก็ตาม

รูปที่ 6 การแสดง MoviesScreen ในการเรียบเรียงเมื่อ ลงในรายการ เนื่องจาก Composable MovieOverview มีรายการที่ไม่ซ้ำกัน โดย Compose จะจดจำอินสแตนซ์ MovieOverview รายการที่ไม่มีการเปลี่ยนแปลง และ สามารถใช้ซ้ำได้ ผลข้างเคียงจะยังคงอยู่

Composable บางรายการมีการรองรับในตัวสำหรับ Composable key ตัวอย่างเช่น LazyColumn ยอมรับการระบุ key ที่กำหนดเองใน items DSL

@Composable
fun MoviesScreenLazy(movies: List<Movie>) {
    LazyColumn {
        items(movies, key = { movie -> movie.id }) { movie ->
            MovieOverview(movie)
        }
    }
}

ข้ามหากอินพุตไม่มีการเปลี่ยนแปลง

ในระหว่างการจัดโครงสร้างใหม่ ฟังก์ชัน Composable ที่มีสิทธิ์บางรายการสามารถมีฟังก์ชัน ข้ามการดำเนินการทั้งหมดหากอินพุตไม่ได้เปลี่ยนแปลงจาก องค์ประกอบ

ฟังก์ชันที่ประกอบกันได้มีสิทธิ์ข้ามเว้นแต่ว่า

  • ฟังก์ชันมีประเภทการแสดงผลที่ไม่ใช่ Unit
  • ฟังก์ชันนี้จะมีคำอธิบายประกอบด้วย @NonRestartableComposable หรือ วันที่ @NonSkippableComposable
  • พารามิเตอร์ที่ต้องระบุเป็นประเภทที่ไม่เสถียร

มีโหมดคอมไพเลอร์เวอร์ชันทดลอง นั่นคือการข้ามแบบเข้มงวด ซึ่งช่วยลดข้อกำหนดสุดท้าย

หากต้องการให้ระบบถือว่าประเภทเสถียรประเภทนั้น ประเภทนั้นต้องเป็นไปตามข้อกำหนดต่อไปนี้ สัญญา:

  • ผลลัพธ์ของ equals สำหรับ 2 อินสแตนซ์จะตลอดไปเหมือนเดิมสำหรับ อินสแตนซ์สองตัวที่เหมือนกัน
  • หากพร็อพเพอร์ตี้สาธารณะของประเภทมีการเปลี่ยนแปลง ระบบจะแจ้งเตือนการเรียบเรียง
  • นอกจากนี้ พร็อพเพอร์ตี้สาธารณะทุกประเภทก็มีความเสถียรด้วย

มีประเภททั่วไปที่สำคัญจำนวนหนึ่งในสัญญานี้ที่ คอมไพเลอร์ของการเขียนจะถือว่ามีความเสถียร แม้ว่าจะไม่มีการกำหนดอย่างชัดแจ้ง ทำเครื่องหมายว่าเสถียรแล้วโดยใช้คำอธิบายประกอบ @Stable:

  • ประเภทค่าพื้นฐานทั้งหมด ได้แก่ Boolean, Int, Long, Float, Char เป็นต้น
  • เครื่องสาย
  • ฟังก์ชันทุกประเภท (lambda)

ทุกประเภทเหล่านี้สามารถทำตาม สัญญาคงที่เนื่องจาก เปลี่ยนแปลงไม่ได้ เนื่องจากประเภทที่เปลี่ยนแปลงไม่ได้ไม่เคยมีการเปลี่ยนแปลง จึงไม่มีความจำเป็นที่จะต้องแจ้งเตือนเลย องค์ประกอบการเปลี่ยนแปลง ทำให้ทำตามสัญญานี้ได้ง่ายขึ้น

ประเภทที่โดดเด่นอย่างหนึ่งที่มีความเสถียรแต่เปลี่ยนแปลงได้คือ MutableState ของ Compose ประเภท หากมีค่าอยู่ใน MutableState ออบเจ็กต์สถานะโดยรวมจะเป็น ถือว่าเสถียรเพราะ Compose จะได้รับการแจ้งเตือนเมื่อมีการเปลี่ยนแปลงเกิดขึ้นกับ พร็อพเพอร์ตี้ .value ของ State

เมื่อมีการส่งทุกประเภทเป็นพารามิเตอร์ไปยัง Composable มีค่าคงที่ พารามิเตอร์ ระบบจะเปรียบเทียบค่าเพื่อความเท่าเทียมตามตำแหน่งที่ Composable ใน UI ต้นไม้ ระบบจะข้ามการจัดองค์ประกอบใหม่หากค่าทั้งหมดไม่มีการเปลี่ยนแปลงตั้งแต่ การโทรก่อนหน้า

การเขียนจะพิจารณาประเภทไฟล์ที่เสถียรก็ต่อเมื่อพิสูจน์ได้ ตัวอย่างเช่น อินเทอร์เฟซมักถือว่าไม่เสถียร และประเภทที่มี "สาธารณะ" ที่เปลี่ยนแปลงได้ พร็อพเพอร์ตี้ที่การติดตั้งใช้งานอาจเปลี่ยนแปลงไม่ได้ก็ไม่เสถียรเช่นกัน

หาก Compose อนุมานไม่ได้ว่าประเภทนั้นเสถียร แต่ต้องการบังคับ เขียนเพื่อทำให้ Chrome เสถียร ให้ทำเครื่องหมายด้วย @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 จะจัดการกับ การใช้งานจะเสถียรหากใช้อินเทอร์เฟซเป็นประเภทพารามิเตอร์