副作用与依赖
处理副作用并注入依赖。
副作用与依赖 是 CoddyKit 上的免费 Swift Academy 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Swift Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Swift Academy 课程共包含 4 节课。
效果为何存在
归约器必须保持纯粹:给定状态和操作后,它们只能修改状态。但真实应用需要网络请求和计时器等副作用。TCA 将这些建模为归约器返回的效果,让状态变更保持纯粹,同时将异步工作分开执行。
正是这种分离让功能易于测试。
import ComposableArchitecture
// Reducer stays pure; side effects are values it returns.
// Returning .none means no further work.返回 .none
当某个操作只改变状态且不需要后续工作时,归约器会返回 .none。这是最常见的效果,表示没有异步工作需要执行。
您之前已经见过它;每个分支最终都可以回退到这个基础效果。
import ComposableArchitecture
Reduce { state, action in
switch action {
case .incrementTapped:
state.count += 1
return .none
}
}使用 Effect.run 执行异步工作
要执行异步工作,您需要返回 .run。它的闭包会接收一个 send 句柄,您可以使用它将结果作为新操作送回系统。
这里,获取操作会加载数据,并在完成后发送响应操作。
import ComposableArchitecture
Reduce { state, action in
switch action {
case .reloadTapped:
return .run { send in
let value = try await loadNumber()
await send(.responseReceived(value))
}
case let .responseReceived(value):
state.count = value
return .none
}
}将结果作为操作送回
效果不能直接修改状态,而是通过 send 发送操作。归约器随后处理这些操作并更新状态。这个循环确保所有状态变更都发生在归约器内部。
因此,网络响应会变成一个响应操作,由归约器保存。
import ComposableArchitecture
.run { send in
let result = try await fetch()
await send(.dataLoaded(result))
}
// .dataLoaded is then handled by the reducer to set state.处理效果中的错误
异步工作可能会抛出错误。您可以在效果内部捕获错误并发送失败操作,让归约器平稳地进入错误状态。
.run 上可选的 catch 闭包会接收抛出的错误。
import ComposableArchitecture
.run { send in
let value = try await loadNumber()
await send(.responseReceived(value))
} catch: { error, send in
await send(.loadFailed)
}依赖为何重要
如果归约器直接调用网络客户端或读取日期,就会变得难以测试和控制。TCA 通过依赖解决了这个问题:这些可注入的服务可以通过 @Dependency 供归约器访问。
在测试中,您可以将它们替换为行为可预测的伪实现。
import ComposableArchitecture
// Instead of calling APIClient.shared directly,
// inject it as a dependency so tests can replace it.@Dependency 属性包装器
在归约器内部,您可以使用 @Dependency 声明依赖,并通过依赖值中的键路径指定其名称。该库提供了时钟和 UUID 生成器等内置依赖。
这里,归约器引入了连续时钟。
import ComposableArchitecture
@Reducer
struct TimerFeature {
@ObservableState
struct State: Equatable { var seconds = 0 }
enum Action { case start, tick }
@Dependency(\.continuousClock) var clock
var body: some ReducerOf<Self> {
Reduce { state, action in .none }
}
}在效果中使用依赖
声明依赖后,您可以在 .run 内部使用它。由于时钟是注入的,测试可以提供一个按需推进时间的测试时钟。
这里,计时器使用注入的时钟每秒跳动一次。
import ComposableArchitecture
var body: some ReducerOf<Self> {
Reduce { state, action in
switch action {
case .start:
return .run { send in
for await _ in self.clock.timer(interval: .seconds(1)) {
await send(.tick)
}
}
case .tick:
state.seconds += 1
return .none
}
}
}定义自定义依赖
您可以让某个值遵循 DependencyKey 并提供 liveValue,以注册自定义依赖。然后,您可以向 DependencyValues 添加一个计算属性,使其获得键路径。
这里,一个接口客户端成为一项一等依赖。
import ComposableArchitecture
struct NumberClient {
var fetch: () async throws -> Int
}
extension NumberClient: DependencyKey {
static let liveValue = NumberClient {
try await loadNumber()
}
}
extension DependencyValues {
var numberClient: NumberClient {
get { self[NumberClient.self] }
set { self[NumberClient.self] = newValue }
}
}注入自定义依赖
设置好键路径后,归约器可以使用 @Dependency(\.numberClient) 声明客户端,并在效果中调用它。生产环境使用实时值;测试可以覆盖该值。
这里,重新加载操作通过注入的客户端获取数据。
import ComposableArchitecture
@Reducer
struct NumberFeature {
@ObservableState
struct State: Equatable { var value = 0 }
enum Action { case reload, response(Int) }
@Dependency(\.numberClient) var numberClient
var body: some ReducerOf<Self> {
Reduce { state, action in
switch action {
case .reload:
return .run { send in
let n = try await numberClient.fetch()
await send(.response(n))
}
case let .response(n):
state.value = n
return .none
}
}
}
}取消副作用
计时器等长时间运行的副作用应该可以取消。您可以为副作用标记一个 cancellable 标识符,之后再从另一个操作中使用 .cancel 取消它。
这样,您就可以按需停止计时器。
import ComposableArchitecture
enum CancelID { case timer }
// Start a cancellable effect:
.run { send in /* ... */ }
.cancellable(id: CancelID.timer)
// Stop it from another action:
.cancel(id: CancelID.timer)快速检查:副作用与结果
回顾一下副作用如何将结果传回给归约器。
回顾:副作用与依赖
您已经学习了 TCA 如何处理副作用和依赖注入:
- 归约器保持纯粹并返回
Effect值;.none表示不执行任何工作。 .run执行异步工作,并通过await send(.action)将结果传回。- 错误通过副作用的
catch闭包处理。 @Dependency注入时钟或您自己的客户端等服务,这些服务通过DependencyKey和DependencyValues声明。- 可以按标识符让长时间运行的副作用变得可
cancellable。
接下来,您将把多个功能组合在一起,并对其进行全面测试。
import ComposableArchitecture
// Recap: pure reducer + Effect.run for async work
// + @Dependency for testable, injectable services.常见问题解答
「副作用与依赖」课时是免费的吗?
是的 — 「副作用与依赖」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Swift Academy 课程的其余内容,请升级到 CoddyKit PRO。 Swift Academy 课程共包含 4 节课。
「副作用与依赖」这节课中我会学到什么?
处理副作用并注入依赖。 你通过在浏览器中直接运行的动手代码来练习 Swift Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Swift Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Swift Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。
「副作用与依赖」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Swift Academy 课中编写并运行代码吗?
能。每节 Swift Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。