Environment
Tiêm phụ thuộc đi xuống theo cây, và thứ gì không thuộc về nó
Environment là một cuốn từ điển được mang xuống dọc theo cây view. Một view ghi vào đó, và mọi view bên dưới đọc được mà những view ở giữa không cần biết nó tồn tại.
Bạn đã dùng nó liên tục rồi. .font(), .foregroundStyle(), .tint(), .lineLimit() và bảng màu
chế độ tối đều là các giá trị environment, và đó là lý do chúng áp lên cả một cây con chứ không phải
lên một view.
@Environment(\.colorScheme) private var colorScheme
@Environment(\.dynamicTypeSize) private var typeSize
@Environment(\.dismiss) private var dismiss
dismiss đáng dừng lại một chút. Nó là một hành động, được tiêm vào bởi bất cứ thứ gì đã trình bày
view này, và view gọi nó không hề biết mình đang nằm trong một sheet, một ngăn xếp điều hướng hay một
popover. Đó là environment làm đúng việc của nó: bên gọi nói cái gì mình muốn, còn ngữ cảnh quyết
định bằng cách nào.
Tự thêm một giá trị
Với @Entry, một dòng là xong:
extension EnvironmentValues {
@Entry var analytics: Analytics = .noOp
}
Rồi tiêm ở trên và đọc ở dưới:
ContentView()
.environment(\.analytics, LiveAnalytics())
// ở bất cứ đâu bên dưới
@Environment(\.analytics) private var analytics
Giá trị mặc định quan trọng hơn vẻ ngoài của nó. Đó là thứ preview nhận được, thứ test nhận được, và
thứ chạy nếu ai đó quên tiêm — nên hãy làm cho nó vô hại và im lặng thay vì một fatalError. Một
client phân tích không làm gì cả là đúng; một cú sập là cái bẫy dành cho người thêm preview tiếp
theo.
Mẹo
Trước khi có @Entry, việc này cần một struct tuân thủ EnvironmentKey với một defaultValue,
cộng thêm một thuộc tính tính toán trên EnvironmentValues. Bạn vẫn sẽ thấy hình dạng đó trong
code có sẵn — vẫn cơ chế ấy, chỉ là viết dài ra.
Đối tượng observable trong environment
Một mô hình @Observable đi vào theo kiểu chứ không theo key path:
ContentView()
.environment(library)
// bên dưới
@Environment(Library.self) private var library
Để ý rằng lần đọc này không phải optional. Nếu không có gì tiêm một Library vào, chỗ này sập lúc
chạy, ngay tại view đọc nó, kèm một thông báo về đối tượng environment bị thiếu. Hãy dùng
@Environment(Library.self) private var library: Library? ở đâu giá trị thật sự là tùy chọn, và nhớ
rằng mọi preview của mọi view nằm dưới điểm tiêm giờ cũng cần đối tượng ấy.
Thứ gì không thuộc về environment
Nó tiện đến mức bị dùng quá tay, và kiểu hỏng thì y hệt kiểu hỏng của biến toàn cục: bạn không nhìn ra các phụ thuộc của một view từ chữ ký của nó.
Bất cứ thứ gì một view cần để hoạt động nên là một tham số. Nếu BookRow không vẽ được khi thiếu
một Book thì hãy nhận vào một Book. Environment dành cho những thứ mang tính bao trùm và có một
giá trị mặc định hợp lý, không dành cho chủ thể của view.
Những giá trị đổi liên tục là một vấn đề hiệu năng. Mọi view đọc một giá trị environment đều được tính lại khi nó đổi, mà những lần đọc ấy lại vô hình ở chỗ gọi, nên một giá trị cập nhật sáu mươi lần một giây có thể vẽ lại nửa ứng dụng vì lý do không ai tìm ra được bằng cách đọc code.
Bất cứ thứ gì mà ở ngôn ngữ khác bạn sẽ gọi là service locator. Hai hoặc ba phụ thuộc cắt ngang — một chủ đề giao diện, một client phân tích, một bộ định dạng ngày — là hợp lý. Mười lăm cái là một container, và một container trong environment nghĩa là không view nào dựng được trong một bài test mà không phải dựng cả thế giới.
Quy tắc tôi dùng: nếu bỏ đi phần tiêm mà đáng ra phải là lỗi biên dịch thì đó là một tham số. Nếu nó nên lặng lẽ lùi về một thứ hợp lý thì đó là một giá trị environment.
Đọc nó ở đúng tầng
Vẫn cái bẫy của chương trước, khoác bộ đồ mới:
struct Screen: View {
@Environment(\.dynamicTypeSize) private var typeSize // cả màn hình phụ thuộc vào nó
var body: some View {
VStack {
Header()
ExpensiveChart()
Footnote(size: typeSize)
}
}
}
Giờ mọi thay đổi cỡ chữ trợ năng đều tính lại Screen, và ExpensiveChart bị dựng lại vì một giá
trị nó không dùng. Hãy chuyển lần đọc @Environment vào trong Footnote, rồi biểu đồ sẽ thôi bận
tâm.
Các lần đọc environment là những phụ thuộc, và chúng là loại dễ bị đặt quá cao nhất, vì khác với một tham số, chúng không để lại dấu vết nào ở chỗ gọi để nhắc bạn.