Trang chủ

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.