一个用于可加载功能的 Swift Composable Architecture 组件。
如果在您的应用程序中使用 The Composable Architecture (TCA),这个小库将允许您在您的功能中集成状态的异步加载。 假设应用程序有一个功能必须加载一些数据来显示给用户。 当我们使用 TCA 时,我们会将这些数据建模为功能的State
,例如
@Reducer
struct WelcomeFeature {
struct State {
let message: String // Load from the server
}
// ...
}
假设在上面的 WelcomeFeature
中,状态的 message
属性将从我们的服务器加载,以便在应用程序启动时显示不同的消息。 在我们应用程序的功能中,我们可以使用 @LoadableState
来实现这一点。 首先,我们可以让状态遵循 Loadable
协议,
extension WelcomeFeature.State: Loadable {
typealias Request = EmptyLoadRequest
}
然后在 AppFeature
中,我们可以组合 WelcomeFeature
。
@Reducer
struct AppFeature {
struct State {
@LoadableStateOf<WelcomeFeature> var welcome
}
enum Action {
case welcome(LoadingActionOf<WelcomeFeature>)
}
var body: some ReducerOf<Self> {
Reduce { state, action in
// main app feature logic
}
.loadable(\.$welcome, action: \.welcome) {
WelcomeFeature()
} load: { state in
WelcomeFeature.State(message: try await fetchWelcomeMessageFromServer())
}
}
}
在上面的例子中,加载函数不需要任何输入。 本质上,它具有 () async throws -> Value
的形式。 然而,在许多情况下,需要提供一个输入,我们称之为请求,即 (Request) async throws -> Value
。 为了做到这一点,在遵循 Loadable
协议时,我们可以指定 Request
类型。
extension WelcomeFeature.State: Loadable {
typealias Request = WelcomeMessageRequest
}
在这种情况下,.loadable()
reducer 修饰符将被请求丰富,如下所示
struct AppFeature {
// ...
var body: some ReducerOf<Self> {
// ...
.loadable(\.$welcome, action: \.welcome) {
WelcomeFeature()
} load: { request, state in
WelcomeFeature.State(
message: try await fetchWelcomeMessage(with: request)
)
}
}
}
为了触发加载,所需要做的就是调用 .load()
动作。 然而,通常在视图中立即加载内容,对于这种情况,提供了一个 SwiftUI View,这使得在视图出现时加载功能变得容易。
struct AppView: View {
let store: StoreOf<AppFeature>
var body: some View {
LoadingView(
loadOnAppear: store.scope(state: \.$welcome, action: \.welcome)
) { store in
Text(store.message) // the welcome message
} onError: { error, request in
Text("Unable to display welcome message, error: \(error.localizedDescription")
} onActive: { request in
ProgressView()
}
}
}
在某些情况下,将 Request
类型耦合到加载的 State
并不理想。 例如,您可能需要从不同的请求驱动相同的“结果列表”功能。 为此,可以直接在 @LoadableState
上指定 Request
类型,例如
@Reducer
struct AppFeature {
struct State {
@LoadableStateWith<String, WelcomeFeature> var welcome
}
enum Action {
case welcome(LoadingActionWith<String, WelcomeFeature>)
}
// ... etc
}
在上面的例子中,不需要让 WelcomeFeature.State
遵循 Loadable
协议,而是我们可以在父功能中指定 Request
类型,在本例中为 String
。
当 Request
不是 EmptyLoadRequest
时,加载视图将需要与上面不同的初始化程序。 在这种情况下,您需要提供原始请求,例如
struct AppView: View {
let store: StoreOf<AppFeature>
var body: some View {
LoadingView(
store.scope(state: \.$welcome, action: \.welcome)
) { store in
Text(store.message) // the welcome message
} onError: { error, request in
Text("Unable to display welcome message, error: \(error.localizedDescription")
} onActive: { request in
ProgressView()
} onAppear: {
store.send(.welcome(.load("Welcome Message Request")))
}
}
}