MutableState — trạng thái có thể quan sát và cơ chế cập nhật trong Compose

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

MutableState là một giao diện trong Jetpack Compose đại diện cho một container chứa giá trị có thể thay đổi và có thể quan sát. Nó là nền tảng của hệ thống phản ứng của Compose: mỗi khi giá trị của MutableState thay đổi qua setter, Compose Runtime thông báo cho tất cả các thành phần đọc và kích hoạt quá trình tái hợp. Theo Google Android Developers, 2026, hiểu về MutableState là bắt buộc để làm việc chính xác với trạng thái trong UI khai báo.

Điểm chính

  • MutableState — giao diện compose.runtime với một thuộc tính value duy nhất (getter + setter)
  • State — giao diện cha chỉ đọc, MutableState thêm khả năng ghi
  • Tái hợp được kích hoạt khi gọi setter của value trong một chu kỳ snapshot đang hoạt động
  • SnapshotMutationPolicy xác định khi nào thay đổi được coi là ý nghĩa đối với tái hợp
  • MutableIntState và tương tự — phiên bản nguyên thủy được tối ưu hóa của MutableState

MutableState trong Jetpack Compose là gì

MutableState là một giao diện từ gói androidx.compose.runtime khai báo một thuộc tính duy nhất: override var value: T. Getter trả về giá trị hiện tại, setter ghi giá trị mới và thông báo cho Compose Runtime về sự thay đổi. Giao diện kế thừa từ State<T>, nơi value chỉ đọc. Kiến trúc hai cấp độ này cho phép phân tách quyền truy cập: một thành phần chỉ cần đọc giá trị nhận State<T>, trong khi thành phần sở hữu nhận MutableState<T>.

Triển khai mặc định của MutableState là lớp nội bộ SnapshotMutableStateImpl, sử dụng cơ chế snapshot để theo dõi các thay đổi. Khi setter của value được gọi, snapshot hiện tại ghi lại việc ghi và đánh dấu tất cả ObservedScope đã đăng ký là không hợp lệ. Các phạm vi này (thường là các hàm Composable) sẽ được tái hợp trong khung hình tiếp theo. Toàn bộ quá trình diễn ra đồng bộ và không có khóa nhờ kiến trúc Lock-free snapshot.

State vs MutableState: State là giao diện chỉ đọc được sử dụng cho các API công khai của thành phần. Khi bạn khai báo một tham số hàm Composable là State<Int>, bạn đảm bảo rằng thành phần có thể đọc nhưng không thể thay đổi trạng thái. MutableState được sử dụng bên trong thành phần sở hữu. Sự phân tách này là một trong những thực hành cơ bản của Compose ngăn chặn các thay đổi trái phép.

Phân cấp State, MutableState và các giao diện dẫn xuất

Phân cấp các giao diện trạng thái trong Compose có nhiều cấp độ. Ở trên cùng là State<T> với value chỉ đọc. Bên dưới là MutableState<T> với value cho phép đọc-ghi. Tiếp theo là các phiên bản nguyên thủy chuyên dụng: MutableIntState, MutableFloatState, MutableLongState, MutableBooleanState và các loại khác, tránh boxing của các kiểu nguyên thủy.

MutableDoubleState và MutableLongState ít phổ biến hơn nhưng cũng tồn tại. Các giao diện tập hợp: MutableListState — để theo dõi thay đổi trong danh sách, MutableStateMap — cho các map. Mỗi giao diện này được tối ưu hóa cho một kịch bản cụ thể và mở rộng MutableState cơ bản với các phương thức thao tác tập hợp bổ sung.

SnapshotStateList và SnapshotStateMap là các triển khai của danh sách và map có thể thay đổi tương thích với snapshot. Chúng cho phép theo dõi không chỉ việc thay thế giá trị mà còn các thay đổi nội bộ: thêm một phần tử vào danh sách, xóa, sửa đổi một phần tử hiện có. Đối với các cấu trúc này, mutableStateListOf() và mutableStateMapOf() tạo các tập hợp có thể quan sát tương ứng.

Giao diệnMục đíchPhương thức tạo
State<T>Container chỉ đọc
MutableState<T>Container đọc-ghimutableStateOf()
MutableIntStateInt nguyên thủy không boxingmutableIntStateOf()
MutableFloatStateFloat nguyên thủy không boxingmutableFloatStateOf()
SnapshotStateListDanh sách có thể quan sátmutableStateListOf()
SnapshotStateMapMap có thể quan sátmutableStateMapOf()

SnapshotMutationPolicy: khi nào kích hoạt tái hợp

SnapshotMutationPolicy là một giao diện xác định khi nào thay đổi trong MutableState được coi là ý nghĩa. mutableStateOf chấp nhận policy làm tham số thứ hai. Các triển khai tiêu chuẩn: structuralEquality() (equals), referentialEquality() (===), neverEqualPolicy() (luôn coi thay đổi là ý nghĩa). Có thể triển khai chính sách tùy chỉnh cho logic riêng.

structuralEquality() — hành vi mặc định. Compose so sánh giá trị mới với giá trị cũ qua equals(). Nếu kết quả là true, tái hợp sẽ KHÔNG được kích hoạt. Điều này thuận tiện cho các kiểu nguyên thủy và data class, nơi hai thể hiện có cùng trường được coi là bằng nhau. Vấn đề: nếu data class chứa List, equals() thực hiện so sánh sâu, có thể tốn kém cho danh sách lớn.

referentialEquality() — so sánh tham chiếu qua ===. Tái hợp chỉ được kích hoạt khi một đối tượng khác được gán, ngay cả khi nội dung giống hệt. Điều này tối ưu cho các data class bất biến, nơi mỗi thể hiện mới đảm bảo một thay đổi. neverEqualPolicy() — luôn coi thay đổi là ý nghĩa mà không thực hiện so sánh. Hữu ích khi setter ít khi được gọi và không cần tốn thời gian cho equals.

kotlin
    // Policy comparison in practice
data class User(val name: String, val age: Int)

@Composable
fun UserProfile() {
    // structuralEquality: recomposition ONLY if data changed
    var user1 by remember {
        mutableStateOf(User("Alice", 30))
    }

    // referentialEquality: recomposition on ANY assignment
    var user2 by remember {
        mutableStateOf(User("Bob", 25),
            SnapshotMutationPolicy.referentialEquality())
    }

    // user1: copy() with same fields does NOT trigger recomposition
    // user2: even user2.copy() == user2 triggers recomposition (new ref)
}

State nguyên thủy: MutableIntState, MutableFloatState, MutableLongState

MutableIntState nguyên thủy và tương tự là các giao diện chuyên dụng lưu trữ các kiểu nguyên thủy mà không boxing. Một MutableState<Int> thông thường lưu trữ Int dưới dạng Integer, tạo ra một đối tượng trên heap mỗi lần ghi. MutableIntState lưu trữ int (nguyên thủy), loại bỏ hoàn toàn chi phí boxing. Điều này đặc biệt quan trọng đối với các cập nhật tần suất cao — bộ đếm, vị trí cuộn, giá trị hoạt hình.

mutableIntStateOf(), mutableFloatStateOf(), mutableLongStateOf() — các hàm tạo MutableState nguyên thủy. Các giao diện được gọi là MutableIntState, MutableFloatState, MutableLongState. Chúng mở rộng MutableState<Int>, MutableState<Float> và MutableState<Long> tương ứng, thêm thuộc tính intValue để truy cập nhanh vào kiểu nguyên thủy. Triển khai nội bộ của chúng sử dụng AtomicInteger cho việc đọc/ghi không khóa.

Sử dụng: bộ đếm (Int), vị trí cuộn (Float offset), dấu thời gian (Long). Trong hầu hết các kịch bản hàng ngày, sự khác biệt về hiệu suất không đáng kể, nhưng trong LazyList với hàng nghìn phần tử và hoạt hình chuyển tiếp, State nguyên thủy mang lại sự cải thiện đáng kể. Google khuyến nghị sử dụng State nguyên thủy cho các kịch bản điển hình thay vì mutableStateOf phổ quát.

kotlin
@Composable
fun ScrollCounter() {
    // Bad: boxing on every update
    var badCount by remember { mutableStateOf(0) }

    // Good: no boxing, primitive storage
    var goodCount by remember { mutableIntStateOf(0) }

    // Usage is identical
    Button(onClick = { goodCount++ }) {
        Text("Count: $goodCount")
    }
}

Ví dụ thực tế làm việc với MutableState

Hãy xem xét một thành phần TodoList, nơi MutableState được sử dụng dưới hai hình thức: dưới dạng các biến riêng biệt cho trạng thái đầu vào và dưới dạng SnapshotStateList cho danh sách công việc động. Cả hai đều sử dụng ủy quyền để mã ngắn gọn.

kotlin
data class TodoItem(val id: Int, val text: String, val isDone: Boolean = false)

@Composable
fun TodoScreen() {
    var inputText by remember { mutableStateOf("") }
    val items = remember { mutableStateListOf() }

    Column(modifier = Modifier.padding(16.dp)) {
        Row {
            TextField(
                value = inputText,
                onValueChange = { inputText = it }
            )
            Button(onClick = {
                if (inputText.isNotBlank()) {
                    items.add(TodoItem(items.size, inputText))
                    inputText = ""
                }
            }) { Text("Add") }
        }

        LazyColumn {
            items(items) { item ->
                Row(modifier = Modifier.fillMaxWidth().clickable {
                    val idx = items.indexOf(item)
                    items[idx] = item.copy(isDone = !item.isDone)
                }) {
                    Checkbox(checked = item.isDone, onCheckedChange = null)
                    Text(item.text)
                }
            }
        }
    }
}

mutableStateListOf tạo một SnapshotStateList — một danh sách có thể thay đổi theo dõi các thay đổi của từng phần tử. Khi items.add() và items[n] = newValue được gọi, Compose thấy sự thay đổi và chỉ tái hợp các phần tử LazyColumn đã thay đổi. inputText là một MutableState<String> thông thường. Sự kết hợp của hai loại MutableState (đơn lẻ và tập hợp) là một mẫu điển hình cho các màn hình có biểu mẫu và danh sách.

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

Tôi có thể sử dụng MutableState mà không có remember không?

MutableState không có remember sẽ được tạo lại mỗi lần tái hợp. Mỗi lần gọi mutableStateOf mới tạo ra một đối tượng mới và giá trị cũ sẽ bị mất. Luôn sử dụng remember để giữ State giữa các lần tái hợp, trừ khi State được tạo bên ngoài Composable (ví dụ, trong ViewModel).

Làm thế nào để chuyển đổi MutableState thành biến thông thường?

Đọc .value một lần bên ngoài snapshot qua snapshot { }. Nhưng điều này vô hiệu hóa tính phản ứng — các thay đổi sẽ không còn kích hoạt tái hợp nữa. Để đọc một lần mà không đăng ký, sử dụng currentValue() bên trong snapshot mà không đọc.

Cái nào nhanh hơn: mutableStateOf hay mutableIntStateOf?

mutableIntStateOf nhanh hơn vì không yêu cầu boxing int thành Integer. Với hàng nghìn cập nhật mỗi giây (hoạt hình, cuộn), sự khác biệt có thể lên tới 30-50% thời gian cấp phát. Đối với các cập nhật hiếm (nhấp chuột, nhập văn bản), sự khác biệt không đáng kể.

Có thể sử dụng MutableState làm đối số của hàm Composable không?

Có thể nhưng không được khuyến nghị. Thay vì MutableState, hãy truyền State (chỉ đọc) + lambda onValueChange. Điều này thực hiện mẫu State Hoisting và làm cho thành phần có thể tái sử dụng. Các thành phần chấp nhận MutableState vi phạm luồng dữ liệu một chiều.

Làm thế nào để tạo triển khai MutableState tùy chỉnh?

Triển khai giao diện MutableState và cung cấp override var value với getter và setter. Trong setter, bạn có thể thêm xác thực hoặc ghi nhật ký. Để tương thích ngược với Compose Runtime, hãy bọc triển khai tùy chỉnh của bạn trong snapshotFlow hoặc sử dụng snapshotIncrement.

Tổng kết

  • MutableState — giao diện cơ bản cho trạng thái có thể thay đổi và quan sát trong Compose
  • State — phiên bản chỉ đọc để truyền dữ liệu mà không có quyền sửa đổi
  • SnapshotMutationPolicy quản lý các điều kiện kích hoạt tái hợp khi thay đổi
  • State nguyên thủy (MutableIntState và các loại khác) loại bỏ chi phí boxing
  • SnapshotStateList và SnapshotStateMap theo dõi các thay đổi nội bộ của tập hợp
  • Tái hợp được kích hoạt tự động khi bất kỳ lời gọi setter nào của value
  • Khuyến nghị: sử dụng MutableState để sở hữu trạng thái và State để truyền xuống

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