วงจรของ Composable

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

ภาพรวมวงจรของลูกค้า

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

รูปที่ 4 การนําเสนอ 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)

        /* ... */
    }
}

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

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

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

การรวมบล็อกโค้ดด้วยการเรียกใช้คีย์ที่คอมโพสิเบิลกับค่าอย่างน้อย 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 และสามารถใช้ซ้ำได้

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

รูปที่ 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)
        }
    }
}

การข้ามหากข้อมูลป้อนไม่มีการเปลี่ยนแปลง

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

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

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

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

ประเภทจะถือว่าเสถียรก็ต่อเมื่อเป็นไปตามข้อตกลงต่อไปนี้

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

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

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

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

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