@State — mục đích và cách sử dụng trong SwiftUI

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

@State là một Property Wrapper trong SwiftUI để quản lý trạng thái cục bộ trong một view duy nhất. SwiftUI tự động vẽ lại view mỗi khi thuộc tính @State thay đổi, làm cho giao diện phản ứng mà không cần gọi cập nhật thủ công. Theo Apple Developer Documentation (2025), @State được khuyến nghị cho các kiểu đơn giản và cấu trúc thuộc về một view duy nhất. @State là cách đơn giản nhất để thêm tương tác vào giao diện SwiftUI.

Các điểm chính

  • @State — Property Wrapper cho trạng thái cục bộ thuộc về một view duy nhất
  • Cập nhật tự động — SwiftUI gọi lại body khi thuộc tính @State thay đổi
  • Kiểu đơn giản — @State hoạt động với String, Int, Bool, enum và struct
  • Không truyền cho view con — dùng @Binding cho thay đổi từ component con
  • private — thuộc tính @State luôn được khai báo với từ khoá private

@State trong SwiftUI là gì?

@State là một Property Wrapper được tích hợp trong SwiftUI cho phép một view lưu trữ và theo dõi trạng thái riêng của nó. Khi một giá trị @State thay đổi, SwiftUI tự động vẽ lại view bằng cách gọi lại thuộc tính body. Đây là nền tảng của lập trình phản ứng trong SwiftUI: nhà phát triển khai báo trạng thái và framework đảm nhận việc đồng bộ hóa giao diện.

@State tạo một vùng lưu trữ trên heap do SwiftUI quản lý. Vùng này tồn tại lâu dài — nó sống sót qua các lần khởi tạo lặp lại của cấu trúc view, xảy ra ở mỗi lần render. SwiftUI sử dụng định danh của view (được tạo từ vị trí của nó trong hệ thống phân cấp) để gắn thuộc tính @State với một view cụ thể. Nhờ đó, trạng thái không bị đặt lại khi view cha được cập nhật.

Một giới hạn quan trọng: @State chỉ dành cho kiểu giá trị (struct, enum, kiểu nguyên thủy). Đối với kiểu tham chiếu (class), hãy sử dụng @StateObject hoặc @ObservedObject. Nếu bạn gán một class cho thuộc tính @State, SwiftUI sẽ không thể phát hiện các thay đổi bên trong đối tượng — chỉ có sự thay thế tham chiếu hoàn toàn.

@State hoạt động như thế nào bên trong?

SwiftUI triển khai @State thông qua cơ chế Storage nội bộ. Mỗi thuộc tính @State nhận được một ô nhớ chuyên dụng được lưu trữ trong một vùng chứa lưu trữ đặc biệt của view. Khi ghi vào wrappedValue, SwiftUI thông báo cho đồ thị phụ thuộc của nó qua didSet về nhu cầu cần vẽ lại.

swift
struct ContentView: View {
    @State private var name: String = "User"
    @State private var isLoggedIn: Bool = false

    var body: some View {
        VStack {
            Text("Xin chào, \(name)")
            Button(isLoggedIn ? "Đăng xuất" : "Đăng nhập") {
                isLoggedIn.toggle()
            }
        }
    }
}

Trong ví dụ, có hai thuộc tính @State: name (String) và isLoggedIn (Bool). Khi isLoggedIn.toggle() được gọi, SwiftUI đánh dấu ContentView là cần cập nhật và gọi lại body trong chu kỳ render tiếp theo. Điểm chính: thuộc tính @State luôn được khai báo với từ khoá private — điều này báo hiệu rằng trạng thái thuộc riêng về view hiện tại và không nên bị thay đổi trực tiếp từ bên ngoài.

Để quan sát các thay đổi, SwiftUI sử dụng CurrentValueSubject từ Combine. Mỗi thuộc tính @State tạo một publisher ẩn thông báo cho hệ thống ở mỗi thay đổi. Điều này cho phép SwiftUI chỉ vẽ lại tập view tối thiểu cần thiết, tránh cập nhật toàn bộ hệ thống phân cấp.

Khi nào sử dụng @State trong dự án

@State là tối ưu cho trạng thái cục bộ đơn giản: trường văn bản trong tìm kiếm, cờ boolean cho cửa sổ modal, công tắc cài đặt, bộ đếm, mục danh sách được chọn. Nếu một giá trị chỉ được sử dụng trong một view và các component con của nó (qua @Binding), @State là lựa chọn đúng đắn. Đối với trạng thái cần tồn tại khi view đóng (ví dụ: dữ liệu biểu mẫu), @State cũng hoạt động miễn là view vẫn còn trong hệ thống phân cấp.

  • Trường văn bản — @State để lưu văn bản nhập trong TextField
  • Cờ boolean — @State để hiển thị/ẩn cửa sổ modal và sheet
  • Chọn phần tử — @State để theo dõi tab hoặc hàng được chọn
  • Bộ đếm — @State cho giá trị số với tăng/giảm
  • Tính toán trung gian — @State để lưu cache kết quả trong view

Không sử dụng @State cho trạng thái ứng dụng toàn cục, lưu cache dữ liệu mạng hoặc đối tượng được sử dụng trên nhiều màn hình. @StateObject và @EnvironmentObject được thiết kế cho các mục đích này. Ngoài ra, @State không phù hợp để lưu trữ lượng lớn dữ liệu — mỗi thay đổi sẽ vẽ lại toàn bộ view.

@State và @Binding: làm việc cùng nhau

@Binding là cầu nối giữa @State trong view cha và view con cần sửa đổi trạng thái đó. Cha khai báo @State, và component con nhận Binding thông qua phép chiếu $. Thay đổi Binding trong view con tự động cập nhật @State trong cha — và ngược lại. Điều này đảm bảo luồng dữ liệu một chiều với khả năng phản hồi.

swift
struct ParentView: View {
    @State private var text: String = ""

    var body: some View {
        ChildView(text: $text)
    }
}

struct ChildView: View {
    @Binding var text: String

    var body: some View {
        TextField("Enter text", text: $text)
    }
}

Trong listing, ParentView sở hữu @State text, và ChildView nhận $text như một Binding. TextField bên trong ChildView liên kết với Binding này qua text: $text. Khi người dùng nhập trong TextField, giá trị thay đổi trong ChildView qua Binding, gây ra cập nhật @State trong ParentView. Cả hai view được vẽ lại với giá trị mới.

Lỗi thường gặp khi làm việc với @State

Lỗi phổ biến nhất là gán một class cho thuộc tính @State. Nếu bạn viết @State var model = MyClass(), SwiftUI sẽ không thể theo dõi các thay đổi của thuộc tính bên trong class — chỉ có sự thay thế đối tượng. Đối với class, luôn sử dụng @StateObject. Vấn đề phổ biến thứ hai là khai báo @State mà không có từ khoá private, vi phạm nguyên tắc đóng gói trạng thái.

Truyền @State trực tiếp cho view con mà không có $ là một lỗi điển hình khác. Nếu bạn truyền TextField(text: text) thay vì TextField(text: $text), component con chỉ nhận được một chuỗi, không phải Binding. Thay đổi văn bản trong TextField sẽ không được đồng bộ với @State của cha. Luôn sử dụng phép chiếu $ để truyền Binding.

Lỗi thứ ba là nhiều thuộc tính @State cho dữ liệu liên quan. Nếu nhiều giá trị logic tạo thành một tổng thể duy nhất (ví dụ: các trường biểu mẫu), hãy kết hợp chúng thành một cấu trúc duy nhất với một @State duy nhất. Điều này đơn giản hóa việc truyền trạng thái cho view con và giảm số lượng trình kích hoạt cập nhật riêng lẻ.

Ví dụ sử dụng @State trong SwiftUI

@State được sử dụng trong hầu hết các dự án SwiftUI cho tương tác cơ bản. Hãy xem xét ví dụ về biểu mẫu đăng nhập nơi @State quản lý các trường văn bản và trạng thái tải. Mẫu này xuất hiện trong mọi ứng dụng — từ ghi chú đơn giản đến giải pháp doanh nghiệp phức tạp.

swift
struct LoginView: View {
    @State private var email: String = ""
    @State private var password: String = ""
    @State private var isLoading: Bool = false
    @State private var errorMessage: String?

    var body: some View {
        Form {
            TextField("Email", text: $email)
            SecureField("Password", text: $password)
            Button("Đăng nhập") {
                login()
            }.disabled(isLoading)
        }
    }

    private func login() {
        isLoading = true
        // Thực hiện yêu cầu mạng
    }
}

Trong ví dụ, có bốn thuộc tính @State: email và password cho các trường biểu mẫu, isLoading để chỉ báo tải và errorMessage để hiển thị lỗi. Mỗi thuộc tính độc lập quản lý phần giao diện của nó. Khi isLoading thay đổi, nút tự động bị vô hiệu hóa qua disabled(isLoading) — không cần cập nhật UI thủ công.

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

Tại sao @State được khai báo với private?

@State được thiết kế cho trạng thái cục bộ của một view cụ thể. Từ khoá private đảm bảo rằng các component khác không thể thay đổi nó trực tiếp, phá vỡ tính đóng gói. Để truy cập từ bên ngoài, hãy sử dụng phép chiếu $.

@State có thể chứa mảng hoặc từ điển không?

Có, @State hỗ trợ mảng và từ điển vì chúng là kiểu giá trị. Tuy nhiên, khi một phần tử của mảng thay đổi, SwiftUI vẽ lại toàn bộ view. Đối với danh sách lớn, @StateObject với @Published hiệu quả hơn.

Điều gì xảy ra khi gán nil cho thuộc tính @State với kiểu Optional?

@State hoạt động chính xác với kiểu Optional. Khi nil được gán, SwiftUI phát hiện thay đổi và vẽ lại view. Điều này thuận tiện cho các trạng thái như errorMessage: String?, nơi nil có nghĩa là không có lỗi.

@State hoạt động như thế nào khi view xuất hiện lại?

@State giữ nguyên giá trị miễn là view vẫn còn trong hệ thống phân cấp. Nếu view bị xóa khỏi hệ thống phân cấp và được thêm lại, @State được khởi tạo lại với giá trị mặc định. Để duy trì lâu dài, hãy sử dụng @AppStorage.

Có thể tạo hoạt ảnh cho các thay đổi của @State không?

Có, hãy bọc thay đổi trong withAnimation: withAnimation(.easeInOut) { isExpanded.toggle() }. SwiftUI tạo hoạt ảnh chuyển tiếp giữa trạng thái giao diện cũ và mới với loại hoạt ảnh được chỉ định.

Tổng kết

  • @State — Property Wrapper cho trạng thái cục bộ của một view, tự động cập nhật giao diện
  • Hoạt động với kiểu đơn giản: String, Int, Bool, cũng như struct và enum
  • Không hoạt động với kiểu tham chiếu (class) — sử dụng @StateObject
  • Luôn private — trạng thái không nên bị thay đổi trực tiếp từ bên ngoài
  • Phép chiếu $ — tạo Binding để truyền quyền thay đổi cho view con
  • Nhiều @State trong một view — thực hành bình thường cho các trạng thái độc lập
  • withAnimation — cho phép tạo hoạt ảnh cho các thay đổi của thuộc tính @State

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