Composition: bản chất, xây dựng cây UI trong Compose

Tác giả: IT Sectr Đã đăng: 2026-06-27 Thời gian đọc: 7 phút

Composition là quy trình trung tâm trong Jetpack Compose, trong đó một cây UI sống được hiển thị trên màn hình được xây dựng từ các hàm Composable mô tả. Không giống như hệ thống View của Android, nơi bố cục được tải từ XML và chuyển đổi thành các đối tượng bất biến, Composition hoạt động như một hệ thống động: các hàm thực thi, tạo slot trong bộ nhớ, hình thành hệ phân cấp nút và liên kết nó với trạng thái. Theo Google Android Developers, 2026, hiểu về Composition là rất quan trọng để tối ưu hiệu suất ứng dụng Compose.

Điểm chính

  • Composition là việc thực thi các hàm Composable để xây dựng cây UI
  • Slot là các ô nhớ lưu trữ tham số và trạng thái của mỗi hàm
  • Vị trí trong Compose (Positional Memorization) liên kết trạng thái với một vị trí trong mã
  • Lần chạy đầu tiên Composition tạo cây UI ban đầu khi màn hình khởi động
  • CompositionLocal truyền dữ liệu qua cây mà không cần tham số rõ ràng

Composition trong Jetpack Compose là gì

Composition là quá trình thực thi các hàm Composable, tạo ra biểu diễn nội bộ của giao diện người dùng dưới dạng cây các nút. Mỗi nút trong cây này tương ứng với một thành phần tích hợp sẵn (Text, Button, Image) hoặc một lời gọi hàm Composable do người dùng định nghĩa. Composition không trực tiếp tạo đối tượng View Android — nó xây dựng một mô tả trừu tượng sau đó được xử lý bởi các giai đoạn Layout và Drawing.

Đặc điểm chính của Composition là khả năng khởi động lại. Mỗi hàm Composable trong thành phần có thể được khởi động lại bất kỳ lúc nào nếu các tham số đầu vào hoặc các đối tượng trạng thái mà nó đọc đã thay đổi. Hệ thống không khởi động lại toàn bộ cây — chỉ những hàm thực sự phụ thuộc vào dữ liệu đã thay đổi.

Về mặt kỹ thuật, Composition được quản lý thông qua Composer — một công cụ nội bộ mà trình biên dịch Kotlin nhúng vào mỗi hàm Composable. Composer ghi vào các slot (nhóm vị trí) thông tin về hàm nào đã được gọi, với tham số nào và theo thứ tự nào. Trong các lần gọi tiếp theo, Composer so sánh dữ liệu mới với dữ liệu đã lưu và quyết định có khởi động lại hay không.

Cây UI được xây dựng như thế nào trong quá trình Composition

Quá trình xây dựng cây UI bắt đầu bằng việc gọi phương thức setContent bên trong Activity hoặc Fragment. Phương thức này tạo Composition ban đầu và bắt đầu thực thi hàm Composable gốc. Sau đó, mỗi hàm Composable lồng nhau thêm các nút của nó vào cây, tạo thành một hệ phân cấp: Row chứa Text và Button, Column chứa Image và Card, v.v.

Mỗi nút cây nhận một khóa vị trí duy nhất, dựa trên vị trí của nó trong mã nguồn. Khóa này được sử dụng để xác định nút trong các lần thực thi tiếp theo. Khóa vị trí là lý do tại sao thứ tự gọi các hàm Composable không nên phụ thuộc vào điều kiện: nếu trong một lần chạy, A -> B được gọi, và trong lần tiếp theo B -> A, Compose sẽ không thể khớp các nút cũ và mới.

kotlin
@Composable
fun AppScreen() {
    Column {                     // Nút Column (vị trí 1)
        HeaderSection()            // Nút HeaderSection (vị trí 2)
        ContentSection()           // Nút ContentSection (vị trí 3)
        FooterSection()            // Nút FooterSection (vị trí 4)
    }
}

@Composable
fun HeaderSection() {
    Row {                       // Nút Row (vị trí 2.1)
        Text("Tiêu đề")         // Nút Text (vị trí 2.2)
        Icon(...)                // Nút Icon (vị trí 2.3)
    }
}

Trong ví dụ này, mỗi lần gọi nhận một vị trí dựa trên thứ tự trong mã. Column (vị trí 1) chứa ba nút con (vị trí 2, 3, 4). HeaderSection thêm hai nút con nữa (2.1, 2.2, 2.3). Nếu trong lần tái tạo tiếp theo, ContentSection được gọi trước HeaderSection, Composer sẽ không thể khớp các nút một cách chính xác — do đó quy tắc: thứ tự gọi các hàm Composable phải ổn định.

Quản lý trạng thái trong Composition

Trạng thái trong Composition được quản lý thông qua các đối tượng kiểu State<T>. Khi một hàm Composable đọc giá trị từ State thông qua thuộc tính được ủy quyền (by), nó đăng ký một sự phụ thuộc vào State đó. Khi giá trị thay đổi, tất cả các hàm đã đọc State này được đánh dấu để khởi động lại trong giai đoạn composition tiếp theo.

Cơ chế đăng ký phụ thuộc được gọi là hệ thống snapshot. Mỗi khi State thay đổi, một snapshot ghi lại tất cả các thay đổi và thông báo cho Composer biết hàm nào phụ thuộc vào State đó. Điều quan trọng cần hiểu: đọc State bên trong mã không phải Composable (ví dụ: trong lambda onClick) không đăng ký phụ thuộc — chỉ đọc bên trong hàm Composable hoặc trong các lambda được thực thi trong ngữ cảnh composition.

Hệ thống snapshot hoạt động theo giao dịch: nhiều thay đổi State trong một sự kiện duy nhất được kết hợp thành một giao dịch, ngăn chặn nhiều lần tái tạo. Điều này đặc biệt quan trọng khi xử lý cử chỉ: một chuyển động thay đổi nhiều đối tượng State, nhưng Compose chỉ thực hiện một lần tái tạo.

kotlin
@Composable
fun StateExample() {
    var text by remember { mutableStateOf("Hello") }
    var isVisible by remember { mutableStateOf(true) }

    Column {
        Text(text)  // đăng ký phụ thuộc vào text

        if (isVisible) {  // đăng ký phụ thuộc vào isVisible
            TextField(value = text, onValueChange = { text = it })
        }

        Button(onClick = { isVisible = !isVisible }) {
            Text(if (isVisible) "Ẩn" else "Hiện")
        }
    }
}

Thay đổi text kích hoạt tái tạo chỉ của Column, Text và TextField. Column, Button và điều kiện isVisible không thay đổi. Sự cô lập tái tạo này là một lợi thế chính của Compose so với các hệ thống vẽ lại toàn bộ màn hình. Mỗi hàm Composable chỉ theo dõi các đối tượng State mà nó đọc trực tiếp.

Composition vs Recomposition: khác biệt chính

Composition và Recomposition là hai chế độ khác nhau để thực thi các hàm Composable. Composition xảy ra một lần khi màn hình được tạo: hệ thống thực thi tất cả các hàm Composable với giá trị ban đầu và xây dựng cây UI ban đầu. Recomposition xảy ra nhiều lần khi dữ liệu thay đổi: hệ thống chỉ khởi động lại các hàm phụ thuộc vào trạng thái đã thay đổi.

Chế độ Composition kích hoạt tất cả các nút cây, cấp phát slot cho mỗi hàm và đăng ký tất cả các nút con. Recomposition hoạt động có chọn lọc: Compose so sánh các giá trị tham số mới và cũ của mỗi hàm, và nếu chúng không thay đổi — hàm không được thực thi (skipping).

Composition và Recomposition khác nhau về chi phí. Composition đầu tiên đắt hơn vì yêu cầu xây dựng toàn bộ cây và cấp phát slot. Recomposition rẻ hơn, đặc biệt nếu hầu hết các hàm ổn định — tham số của chúng được so sánh bằng equals và Compose bỏ qua lời gọi. Để có hiệu suất tối đa, bạn nên cố gắng để hầu hết các lần tái tạo chỉ ảnh hưởng đến càng ít hàm càng tốt.

Đặc điểmCompositionRecomposition
Khi nào xảy raMột lần, khi hiển thị lần đầuNhiều lần, khi dữ liệu thay đổi
Phạm viToàn bộ câyChỉ các hàm đã thay đổi
So sánh tham sốKhông thực hiệnThực hiện để bỏ qua
Tạo slotCó, tất cả slot được tạoChỉ cho nút mới

CompositionLocal: truyền dữ liệu qua cây

CompositionLocal là một cơ chế truyền dữ liệu ngầm qua cây composition. Nó giải quyết vấn đề khi một tham số cần được truyền qua hàng chục hàm Composable lồng nhau không sử dụng nó trực tiếp. Thay vì một chuỗi tham số rõ ràng, dữ liệu được đặt ở cấp cao nhất và được đọc trong bất kỳ hàm lồng nhau nào thông qua CompositionLocal.current.

MaterialTheme là ví dụ nổi tiếng nhất về CompositionLocal. Tất cả các thành phần Compose đọc màu sắc, kiểu chữ và hình dạng thông qua MaterialTheme.colorScheme, MaterialTheme.typography, MaterialTheme.shapes mà không nhận chúng qua tham số. Các nhà phát triển có thể tạo CompositionLocal của riêng họ cho dữ liệu như người dùng hiện tại, cài đặt bản địa hóa hoặc cấu hình màn hình.

Một hạn chế quan trọng: CompositionLocal không nên được sử dụng cho dữ liệu thay đổi thường xuyên (vị trí cuộn, văn bản trong trường nhập). Một thành phần đọc CompositionLocal sẽ khởi động lại mỗi khi giá trị thay đổi, vì vậy đối với dữ liệu động, tốt hơn nên sử dụng tham số rõ ràng hoặc State. CompositionLocal là tối ưu cho dữ liệu cấu hình hiếm khi hoặc không bao giờ thay đổi.

kotlin
val LocalUser = compositionLocalOf<User?> { null }

@Composable
fun AppRoot(user: User, content: @Composable () -> Unit) {
    CompositionLocalProvider(LocalUser.provides(user)) {
        content()
    }
}

@Composable
fun UserAvatar() {
    val user = LocalUser.current  // đọc mà không có tham số rõ ràng
    AsyncImage(model = user?.avatarUrl, contentDescription = "Avatar")
}

CompositionLocalProvider tạo một phạm vi trong đó LocalUser.current trả về giá trị được chỉ định. UserAvatar đọc người dùng mà không cần truyền tham số rõ ràng qua các hàm trung gian. Điều này đặc biệt có giá trị trong các hệ phân cấp sâu nơi dữ liệu chỉ cần thiết ở một vài nút lá.

Câu hỏi thường gặp

Điều gì xảy ra nếu State bị thay đổi trong quá trình Composition

Thay đổi State trong quá trình Composition sẽ lên lịch một lần tái tạo mới, sẽ được thực thi sau khi lần hiện tại hoàn tất. Không có vòng lặp vô hạn nào xảy ra: Compose đảm bảo rằng mỗi lần tái tạo được thực hiện trong một giao dịch riêng biệt của hệ thống snapshot.

Composition của một màn hình phức tạp mất bao lâu

Trên các thiết bị hiện đại, Composition của màn hình với 50–100 hàm Composable mất 1–5 ms. Google khuyến nghị giới hạn trong 16 ms cho một khung hình 60fps. Nếu Composition vượt quá giới hạn này, hãy sử dụng LazyColumn hoặc chia màn hình thành các hàm nhỏ hơn.

Có thể bắt đầu Composition theo cách thủ công không

Không thể bắt đầu Composition trực tiếp theo cách thủ công — nó được quản lý bởi Composer tự động. Tuy nhiên, bạn có thể buộc tái tạo bằng cách thay đổi State hoặc gọi invalidate() trên composable gốc nếu bạn có quyền truy cập vào CompositionContext.

Composition khác với hệ phân cấp View trong Android cổ điển như thế nào

Hệ phân cấp View là một cây bất biến của các đối tượng Java được tạo một lần. Composition là một cây ảo được xây dựng lại mỗi khi dữ liệu thay đổi. View lưu trữ trạng thái trong các biến thể hiện, Composition — trong các slot liên kết với vị trí gọi hàm.

Composition xử lý việc xóa nút như thế nào

Nếu một hàm Composable không còn được gọi nữa (ví dụ: điều kiện if trở thành false), Composition sẽ xóa nút của nó và kích hoạt dọn dẹp DisposableEffect. Khi nó xuất hiện trở lại (if lại trở thành true), một nút mới được tạo — nút cũ không được khôi phục.

Tổng kết

  • Composition là quá trình thực thi các hàm Composable để xây dựng cây UI liên kết với trạng thái
  • Composer quản lý slot, ghi lại các lời gọi hàm và so sánh tham số trong quá trình tái tạo
  • Hệ thống snapshot đăng ký sự phụ thuộc của hàm vào State và kết hợp các thay đổi thành giao dịch
  • Composition thực thi một lần khi khởi động, Recomposition — khi dữ liệu thay đổi
  • CompositionLocal truyền dữ liệu cấu hình qua cây mà không cần chuỗi tham số rõ ràng
  • Vị trí của một lời gọi hàm đóng vai trò là định danh duy nhất của nó trong cây composition
  • Khuyến nghị: giữ các hàm Composable nhỏ với tham số bất biến để bỏ qua hiệu quả

Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay

IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.

Thảo luận dự án

Đọc thêm