SwiftUI 和 Distant Child 查看依赖注入

SwiftUI & Distant Child View Dependency Injection

我正在尝试了解如何使用 SwiftUI 将依赖项注入 child 视图。

例如,如果我有一个视图 -

struct FeedListView: View {
    
    @ObservedObject private(set) var viewModel: FeedViewModel
    @State private var items: [Post] = []
    
    var body: some View {
        ScrollView(.vertical, showsIndicators: false) {
                ForEach(items, id: \.id) { item in
                    FeedListItemView(viewModel: .init(item: item))
                }
        }
        .onAppear(perform: onLoad)
        .padding()
    }
}

private extension FeedListView {
    
    func onLoad() {
        viewModel.onFeedLoad = { self.items = [=11=] }
        viewModel.loadFeed()
    }
}

带有视图模型 -

final class FeedViewModel: ObservableObject {
    
    typealias LoaderResult = Result<[Post], Error>
    typealias LoaderCompletion = (LoaderResult) -> Void
    typealias Loader = ((@escaping LoaderCompletion) -> Void)
    
    typealias Observer<T> = (T) -> Void
     
    @Published var onFeedLoad: Observer<[Post]>?
    @Published var onLoadingStateChange: Observer<Bool>?

    private let loader: Loader
    
    init(loader: @escaping Loader) {
        self.loader = loader
    }
    
    func loadFeed() {
        onLoadingStateChange?(true)
        loader { [weak self] result in
            if let feed = try? result.get() {
                self?.onFeedLoad?(feed)
            }
            self?.onLoadingStateChange?(false)
        }
    }
}

由于这是顶层视图,我可以在组合点注入 FeedViewModel 及其依赖项。

但是这个视图呈现 FeedListItemView - 该视图有它自己的视图模型并且会有它自己的依赖项。

final class FeedListItemViewModel: ObservableObject {
        
    private let item: Post
    
    init(item: Post) {
        self.item = item
    }
    
    func loadImage() {
        let imageURL = item.imageURL
        // do something here to load an image
    }
}

struct FeedListItemView: View {
    
    @ObservedObject private(set) var viewModel: FeedListItemViewModel

    init(viewModel: FeedListItemViewModel) {
        self.viewModel = viewModel
    }
    
    var body: some View {
        Text("My Awesome Post")
            .onAppear(perform: viewModel.loadImage)
    }
}

如何在 FeedListItemViewModel 中提供图像加载器,例如 (_imageURL: URL) -> AnyPublisher<Data, Error>,而不将其传递到 FeedListView,然后将其直接传递到 child views 视图模型?

可测试性非常重要,所以我想避免依赖项的单例实例,我也想避免将一堆值传递给视图,只为该视图将它们传递给 child,这又将它们传递给另一个遥远的 child.

对于如何实现这一目标的任何想法,我将不胜感激。

您可以使用@EnvironmentObject 包装器将视图模型从祖先视图分发到其子视图和孙视图。

如果您在应用文件中实例化 FeedListView,请添加此修饰符:

.environmentObject(YourViewModel())

在最上面的视图中实例化它之后,您可以使用@EnvironmentObject 包装器来访问环境对象。

@EnvironmentObject var yourVM: YourViewModel

但是,环境对象只创建一次,它们的工作方式类似于单例。当您在任何视图中进行更改时,其他视图会看到更改后的对象。