mutableStateOf: tạo trạng thái có thể quan sát và phản ứng trong Compose

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

mutableStateOf là một hàm trong Jetpack Compose tạo ra một vùng chứa trạng thái có thể thay đổi và quan sát được. Khi giá trị bên trong vùng chứa này thay đổi, Compose tự động kích hoạt quá trình tái tổ hợp của tất cả các thành phần đang đọc trạng thái này. Nếu không có mutableStateOf, giao diện người dùng không thể cập nhật một cách phản ứng khi dữ liệu thay đổi. Theo Google Android Developers, 2026, mutableStateOf là khối xây dựng chính cho trạng thái cục bộ trong Compose.

Những điểm chính

  • mutableStateOf tạo vùng chứa MutableState được Compose Runtime theo dõi
  • Tái tổ hợp được kích hoạt tự động khi value của đối tượng State này thay đổi
  • Ủy quyền qua var cho phép sử dụng mutableStateOf mà không cần truy cập .value
  • Khóa trong remember(mutableStateOf) không cần thiết — State tự thông báo cho Compose về các thay đổi
  • Hệ thống Snapshot đảm bảo tính nhất quán khi đọc trong môi trường đa luồng

mutableStateOf trong Jetpack Compose là gì

mutableStateOf là một hàm từ gói compose.runtime tạo ra một đối tượng MutableState<T> lưu trữ giá trị và có thể thông báo cho Compose Runtime về các thay đổi. Chữ ký: fun <T> mutableStateOf(value: T, policy: SnapshotMutationPolicy<T> = structuralEquality()): MutableState<T>. Tham số policy xác định khi nào một thay đổi được coi là quan trọng — khi bằng nhau về cấu trúc, bằng nhau về tham chiếu, hoặc không bao giờ.

MutableState là một giao diện với một thuộc tính duy nhất value: một getter để đọc và một setter để ghi. Khi setter được gọi, Compose Runtime ghi lại thay đổi trong một snapshot và đánh dấu tất cả các hàm Composable đang đọc biến State này là cần tái tổ hợp. Quá trình này diễn ra đồng bộ trong một chu kỳ snapshot duy nhất, loại bỏ các trạng thái trung gian trong quá trình thay đổi theo tầng.

Tham số policy — đối số thứ hai của mutableStateOf, xác định hành vi so sánh. structuralEquality() kiểm tra equals() — đây là hành vi mặc định. referentialEquality() kiểm tra === (bằng nhau về tham chiếu). neverEqual() coi mọi gán giá trị là một thay đổi. Việc chọn policy ảnh hưởng đến việc liệu tái tổ hợp có được kích hoạt khi gán cùng một giá trị hay không.

Cú pháp và cách khai báo mutableStateOf

Cách đơn giản nhất để khai báo trạng thái có thể quan sát là sử dụng mutableStateOf với remember. Nếu không có remember, mỗi lần tái tổ hợp sẽ tạo ra một State mới và tất cả các thay đổi trước đó sẽ bị mất. remember đảm bảo rằng cùng một MutableState tồn tại qua một loạt các lần tái tổ hợp miễn là hàm Composable vẫn nằm trong thành phần.

kotlin
@Composable
fun Counter() {
    // Không ủy quyền: đọc/ghi qua .value
    val count = remember { mutableStateOf(0) }
    Button(onClick = { count.value++ }) {
        Text("Count: ${count.value}")
    }
}

@Composable
fun CounterDelegated() {
    // Có ủy quyền: var + by = Property Delegation
    var count by remember { mutableStateOf(0) }
    Button(onClick = { count++ }) {
        Text("Count: $count")
    }
}

Sự khác biệt giữa hai cách tiếp cận là về mặt cú pháp. Property Delegation (by) sử dụng một quy ước của Kotlin: trình biên dịch tạo ra các lệnh gọi getValue() và setValue() để đọc và ghi. Điều này tương đương với việc truy cập trực tiếp count.value nhưng trông giống như làm việc với một biến thông thường. Cả hai cách tiếp cận đều giống hệt nhau về chức năng: Compose theo dõi việc đọc trong getter và ghi trong setter bất kể ký hiệu nào.

Hình thứcĐọcGhi
Không ủy quyềnval count = mutableStateOf(0)count.valuecount.value = n
Có ủy quyềnvar count by mutableStateOf(0)countcount = n

Thuộc tính ủy quyền và var

Cơ chế thuộc tính ủy quyền của Kotlin không phải là tính năng của Compose mà là khả năng tích hợp sẵn của ngôn ngữ. Bất kỳ lớp nào cũng có thể triển khai các toán tử getValue(thisRef, property) và setValue(thisRef, property, value), sau đó thể hiện của nó có thể được sử dụng với từ khóa by. MutableState hoạt động chính xác như vậy: getValue trả về giá trị hiện tại và setValue gán một giá trị mới.

Một điểm khác biệt quan trọng: val so với var. mutableStateOf có thể được gán cho cả val và var. Với val (val count = mutableStateOf(0)), bản thân đối tượng MutableState là bất biến, nhưng thuộc tính value của nó có thể thay đổi. Với var (var count by mutableStateOf(0)), ủy quyền tạo ra ảo giác làm việc với một kiểu nguyên thủy, nhưng setter thực sự gọi setValue trên MutableState. Việc chọn giữa val và var là chọn giữa truy cập tường minh và truy cập ngầm định vào .value.

State Ủy quyền là đường cú pháp giúp đơn giản hóa mã nhưng không thay đổi cơ chế. Trình biên dịch Kotlin dịch var x by mutableStateOf(0) thành getter/setter gọi mutableStateOf.getValue() và mutableStateOf.setValue(). Trong bytecode được tạo, không có sự khác biệt giữa val và var với by — cả hai đều hoạt động thông qua cùng một vùng chứa MutableState.

kotlin
    // Đại diện tùy chỉnh cho Compose State
class ValidatedState<T>(initialValue: T) {
    private val state = mutableStateOf(initialValue)

    operator fun getValue(thisRef: Any?, property: KProperty<*>) = state.value

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        if (value != state.value) {
            state.value = value
        }
    }
}

@Composable
fun Test() {
    var text by remember { ValidatedState("") }
}

Hệ thống Snapshot: cách mutableStateOf hoạt động bên trong

Snapshot là một cơ chế của Compose Runtime đảm bảo tính nhất quán khi đọc State trong quá trình thay đổi song song. Khi một hàm Composable đọc mutableStateOf, snapshot ghi lại giá trị hiện tại. Nếu trong quá trình thành phần, một thay đổi khác ghi vào cùng State đó, snapshot thấy việc ghi nhưng không cho phép đọc dữ liệu không nhất quán — việc đọc luôn trả về giá trị hợp lệ tại thời điểm bắt đầu snapshot.

Khi setter mutableStateOf.value = newValue được gọi, Compose Runtime không kích hoạt tái tổ hợp ngay lập tức. Thay vào đó, thay đổi được đăng ký trong snapshot hiện tại. Khi snapshot được áp dụng (tại ranh giới khung hình), Compose duyệt qua danh sách các State đã thay đổi và đánh dấu các thành phần đang đọc là Invalid. Chỉ trong khung hình tiếp theo, quá trình tái tổ hợp mới bắt đầu. Điều này đảm bảo rằng giao diện người dùng không bị vẽ lại hàng chục lần trong quá trình thay đổi theo tầng.

Snapshot toàn cục và cục bộ: theo mặc định, mutableStateOf hoạt động trong snapshot toàn cục, được áp dụng tự động. Bạn có thể tạo một snapshot cục bộ qua Snapshot.takeSnapshot() để đọc biệt lập mà không có tác dụng phụ. Điều này được sử dụng bên trong Modifier khi bạn cần đọc State mà không đăng ký theo dõi thay đổi. Cách tiếp cận này tối ưu hóa hiệu suất và ngăn chặn các tái tổ hợp không mong muốn.

Ví dụ sử dụng mutableStateOf

Hãy xem xét một kịch bản thực tế — một biểu mẫu đăng nhập với ba trường: email, mật khẩu và trạng thái tải. Cả ba trường đều sử dụng mutableStateOf, nhưng với các policy khác nhau và các mức lồng nhau khác nhau. email sử dụng ủy quyền, password sử dụng truy cập trực tiếp.

kotlin
data class LoginState(
    val email: String = "",
    val password: String = "",
    val isLoading: Boolean = false,
    val error: String? = null
)

@Composable
fun LoginForm(onLogin: (String, String) -> Unit) {
    // State đơn cho biểu mẫu, policy = referentialEquality
    var formState by remember {
        mutableStateOf(LoginState(), SnapshotMutationPolicy.referentialEquality())
    }

    val isValid = remember(formState) {
        formState.email.contains("@") && formState.password.length() >= 6
    }

    Column(modifier = Modifier.padding(16.dp)) {
        OutlinedTextField(
            value = formState.email,
            onValueChange = { formState = formState.copy(email = it) },
            label = { Text("Email") }
        )
        OutlinedTextField(
            value = formState.password,
            onValueChange = { formState = formState.copy(password = it) },
            label = { Text("Mật khẩu") },
            visualTransformation = PasswordVisualTransformation()
        )
        Button(
            onClick = { onLogin(formState.email, formState.password) },
            enabled = isValid
        ) {
            Text("Đăng nhập")
        }
    }
}

Trong ví dụ này, mutableStateOf được sử dụng với một lớp dữ liệu tùy chỉnh LoginState và policy referentialEquality. Điều này có nghĩa là tái tổ hợp sẽ chỉ được kích hoạt khi một thể hiện LoginState mới được gán qua copy(). isValid được tính toán dựa trên formState và chỉ được tính toán lại khi nó thay đổi. Cách tiếp cận này cung cấp khả năng kiểm soát rõ ràng đối với các tái tổ hợp: mỗi trường biểu mẫu chỉ thay đổi thông qua việc tạo một bản sao mới.

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

mutableStateOf khác StateFlow như thế nào?

mutableStateOf là vùng chứa dành riêng cho Compose hoạt động bên trong các snapshot. StateFlow đến từ kotlinx.coroutines.flow và không bị ràng buộc với Compose. mutableStateOf tự động kích hoạt tái tổ hợp, trong khi StateFlow yêu cầu collectAsState(). Đối với trạng thái giao diện người dùng bên trong Composable, mutableStateOf được ưu tiên hơn.

Có thể sử dụng mutableStateOf bên ngoài hàm @Composable không?

Có, mutableStateOf có thể được gọi bên ngoài hàm Composable, nhưng nó sẽ không được theo dõi. Để có phản ứng trong giao diện người dùng, State phải được đọc bên trong Composable. Nhiều ViewModel sử dụng MutableStateField (một lớp bao bọc xung quanh mutableStateOf) để truyền trạng thái đến giao diện người dùng qua StateFlow.

Điều gì xảy ra khi State bị thay đổi đồng thời từ hai luồng?

Hệ thống Snapshot đảm bảo tính nhất quán: mỗi lần tái tổ hợp thấy một trạng thái nhất quán tại thời điểm bắt đầu snapshot. Các thay đổi từ các luồng khác nhau được áp dụng nguyên tử tại ranh giới khung hình, loại bỏ các điều kiện tranh chấp khi đọc trong một thành phần duy nhất.

Làm thế nào để đặt lại mutableStateOf về giá trị ban đầu?

Gán một giá trị mới: count.value = 0 (hoặc count = 0 với ủy quyền). Nếu bạn cần tạo lại State hoàn toàn, hãy sử dụng remember với một khóa: remember(key) { mutableStateOf(initial) } — khi khóa thay đổi, State sẽ được tạo mới.

mutableStateOf có ảnh hưởng đến hiệu suất khi thay đổi thường xuyên không?

Composer sử dụng các snapshot nhóm các thay đổi: ngay cả với hàng trăm lần gán trong một khung hình duy nhất, quá trình tái tổ hợp chỉ chạy một lần. Đối với các cập nhật rất thường xuyên (hoạt ảnh), hãy sử dụng Animatable hoặc animate*AsState — chúng được tối ưu hóa cho các cập nhật theo từng khung hình.

Tóm tắt

  • mutableStateOf tạo vùng chứa MutableState có thể quan sát được theo dõi bởi Compose Runtime
  • Ủy quyền qua by giúp đơn giản hóa mã nhưng không thay đổi cơ chế của State
  • Hệ thống Snapshot đảm bảo tính nhất quán khi đọc và ngăn chặn các tái tổ hợp không cần thiết
  • Policy xác định khi nào một thay đổi được coi là quan trọng — structuralEquality, referentialEquality, neverEqual
  • remember là bắt buộc để giữ State giữa các lần tái tổ hợp trong Composable
  • Khuyến nghị: sử dụng mutableStateOf với ủy quyền cho trạng thái giao diện người dùng và referentialEquality cho các lớp dữ liệu
  • Tránh: tạo mutableStateOf mà không có remember — mỗi lần gán sẽ tạo một đối tượng mới

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