Observation
SwiftUI biết một view thật sự đọc những thuộc tính nào bằng cách nào
@Observable trông như một cách viết ngắn hơn của ObservableObject. Nó là một cơ chế khác, và
chính sự khác biệt ấy mới là điều đáng nói.
@Observable
final class Library {
var books: [Book] = []
var searchText = ""
var isSyncing = false
}
Không @Published, không ObservableObject, không objectWillChange. Macro viết lại mọi thuộc tính
lưu trữ thành thuộc tính tính toán, thứ báo cáo mọi lần đọc và ghi cho một ObservationRegistrar.
Mô hình cũ thông báo; mô hình mới được hỏi
Với ObservableObject, đối tượng bắn objectWillChange ở mọi lần ghi vào một @Published, và mọi
view đang quan sát nó đều vẽ lại — bất kể nó có động đến thuộc tính vừa đổi hay không. Một view
chỉ hiện books vẫn vẽ lại mỗi lần isSyncing lật hoặc mỗi lần gõ một ký tự vào searchText.
Với @Observable, SwiftUI tính body bên trong một phạm vi theo dõi. Mọi thuộc tính được đọc trong
lần tính đó đều được ghi nhận, và view chỉ bị làm mất hiệu lực khi một trong những thuộc tính ấy
thay đổi.
struct BookList: View {
let library: Library
var body: some View {
List(library.books) { BookRow(book: $0) } // chỉ đọc `books`
}
}
Gõ vào ô tìm kiếm không còn vẽ lại view này nữa. Không có gì phải cấu hình để đạt được điều đó — nó
tự rơi ra từ việc body tình cờ đọc những thuộc tính nào.
Mẹo
Việc theo dõi diễn ra theo từng lần tính, không phải theo kiểu view. Một view chỉ đọc một thuộc
tính bên trong một nhánh if thì không theo dõi nó khi nhánh ấy sai. Vào nhánh đó, và phụ thuộc
xuất hiện ở lần cập nhật kế tiếp.
Viết gì, và thôi viết gì
Wrapper đặt trên thuộc tính được quyết định bởi việc view làm gì với đối tượng, chứ không bởi bản thân đối tượng:
struct LibraryScreen: View {
@State private var library = Library() // view này sở hữu nó
var body: some View {
BookList(library: library) // cứ truyền đi; đọc thì không cần wrapper
SearchField(library: library)
}
}
struct SearchField: View {
@Bindable var library: Library // cần một binding vào bên trong
var body: some View {
TextField("Tìm kiếm", text: $library.searchText)
}
}
- Không gì cả — đọc thôi là đủ, và đây là trường hợp phổ biến.
@State— view này tạo ra đối tượng và nó nên sống đúng bằng đời của view.@Bindable— bạn cần$model.propertycho mộtTextField,Togglehay tương tự.@Environment(Library.self)— nó được tiêm từ trên xuống.
@ObservedObject, @StateObject, @EnvironmentObject và @Published là thế hệ trước. Bạn sẽ gặp
chúng trong code có sẵn và không cần vội chuyển đổi, nhưng không nên có thứ gì mới dùng đến chúng.
Chỗ việc theo dõi hỏng lặng lẽ
Observation chỉ thấy được thứ nó chặn được, và có ba khoảng hở thật sự.
Thuộc tính tính toán dựa trên chỗ chứa không được quan sát. Một thuộc tính tính toán đọc một
private var thuần mà macro không viết lại thì vô hình; chẳng có gì báo cáo lần đọc đó.
Đọc bên ngoài body. Việc theo dõi được thiết lập trong lúc body đang được tính. Một thuộc
tính đọc bên trong closure của Task, trong completion handler hay trong onAppear không phải là
một phụ thuộc, và sửa nó về sau sẽ chẳng vẽ lại gì cả.
Tập hợp các đối tượng quan sát được. library.books theo dõi cái mảng — chèn vào, xóa đi, đảo
thứ tự. Nó không theo dõi một thuộc tính bên trong từng Book. Nếu Book là một class, hãy đánh
dấu nó @Observable luôn rồi để mỗi dòng tự đọc nó, và đó cũng là điều bạn muốn: chỉ đúng dòng đó
vẽ lại.
struct BookRow: View {
let book: Book // class @Observable
var body: some View {
Text(book.title) // chỉ dòng này vẽ lại khi tiêu đề đổi
}
}
Cái cắn đau nhất
Truyền cả một mô hình xuống rồi đọc nó ở trên cùng:
struct Dashboard: View {
let library: Library
var body: some View {
let count = library.books.count // một lần đọc, ngay tại Dashboard
Header(count: count)
BookList(library: library)
}
}
Dashboard giờ phụ thuộc vào books, nên nó được tính lại ở mọi thay đổi của mảng — và cả cây con
của nó bị dựng lại theo. Hãy đẩy lần đọc ấy xuống view thật sự cần nó, rồi Dashboard sẽ thôi bận
tâm.
Đó vẫn là quy tắc “trạng thái nằm ở view thấp nhất cần đến nó” từ phần mở đầu, ở dạng nó thường xuất hiện trong code thật: không phải trạng thái được lưu ở đâu, mà là nó được đọc ở đâu.