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
但是,环境对象只创建一次,它们的工作方式类似于单例。当您在任何视图中进行更改时,其他视图会看到更改后的对象。
我正在尝试了解如何使用 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
但是,环境对象只创建一次,它们的工作方式类似于单例。当您在任何视图中进行更改时,其他视图会看到更改后的对象。