iOS SwiftUI 状态与数据流
iOS SwiftUI 状态与数据流
SwiftUI 通过状态变化重新计算视图描述。页面出现重复请求、输入回退或对象被反复创建,通常不是渲染机制本身的问题,而是状态所有权没有定义清楚。每一份可变状态都应有唯一持有者,子视图通过值或绑定读取与请求修改。
一、先确定状态所有者
可以先问三个问题:
- 谁创建这份状态。
- 谁决定它何时销毁。
- 哪些视图只能读取,哪些视图可以修改。
局部开关由当前视图持有,页面业务状态由页面模型持有,全局账户状态由应用级容器持有。不要因为多个地方要读取,就把所有状态提升到全局。
二、State 管理局部值
struct FilterChip: View {
@State private var expanded = false
var body: some View {
Button(expanded ? "收起" : "展开") {
expanded.toggle()
}
}
}
State 的存储由 SwiftUI 管理,适合视图内部且生命周期一致的简单值。初始化后不应把它当成持续同步外部参数的普通属性。
三、Binding 传递修改能力
子视图不拥有值,但需要修改父级状态时,可以接收绑定:
struct QuantityEditor: View {
@Binding var quantity: Int
var body: some View {
HStack {
Button("减少") {
quantity = max(1, quantity - 1)
}
Text(String(quantity))
Button("增加") {
quantity += 1
}
}
}
}
如果子视图只需要触发一个动作,而不需要任意修改值,传入语义化闭包通常比暴露整个绑定更安全。
四、StateObject 持有引用模型
视图负责创建观察对象时,使用 StateObject 保持对象身份:
struct OrderListScreen: View {
@StateObject private var viewModel: OrderListViewModel
init(repository: OrderRepository) {
_viewModel = StateObject(
wrappedValue: OrderListViewModel(repository: repository)
)
}
var body: some View {
OrderListContent(state: viewModel.state)
.task {
await viewModel.loadIfNeeded()
}
}
}
如果对象由父级创建并传入,子视图使用 ObservedObject 观察即可,不应再次声明所有权。
五、保持页面状态互斥
enum OrderListState: Equatable {
case idle
case loading
case content([OrderRowModel])
case empty
case failed(String)
}
一个枚举比多个布尔属性更能表达页面当前阶段。新增状态时,switch 会提示未处理分支。
页面模型应位于主执行域:
@MainActor
final class OrderListViewModel: ObservableObject {
@Published private(set) var state: OrderListState = .idle
private let repository: OrderRepository
func loadIfNeeded() async {
guard case .idle = state else { return }
await load()
}
}
六、避免在 body 中执行副作用
body 可能被多次计算,不应在其中直接发起请求、写存储或发送统计。副作用放在 task、事件闭包或页面模型方法中,并保证重复触发策略明确。
标识变化时可以让任务随标识取消并重新执行,但只有业务确实需要跟随该值刷新时才使用。
七、区分派生数据与原始状态
能从现有状态计算出的值无需重复保存:
var totalPriceText: String {
priceFormatter.string(
fromCent: items.reduce(0) { $0 + $1.totalInCent }
)
}
计算昂贵时可以在模型转换阶段缓存结果,但不要同时维护多个可能不一致的真相来源。
八、环境适合稳定共享依赖
主题、账户会话或导航协调器可以通过环境传递,但环境值过多会隐藏组件真实依赖。业务核心依赖优先通过初始化参数明确传入。
预览和测试中也要提供所需环境值,否则视图可能只在完整应用环境中可用。
九、处理一次性事件
弹窗、导航和提示不是长期页面状态。如果把“是否跳转”作为可重复观察的布尔值,视图重建后可能再次执行。
可以让路由由协调者处理,或把流程建模为明确阶段,并在动作完成后消费。选择应遵循项目现有导航架构。
十、兼容与测试
SwiftUI 状态相关能力随系统版本演进。项目最低版本不支持的新属性包装器不能直接使用,应沿用现有 ObservableObject 等兼容方案。
测试重点包括:
- 视图重新计算时模型不会重复创建。
- 首次进入只加载一次。
- 参数改变后是否按业务刷新。
- 子视图只能修改允许状态。
- 取消任务不会显示普通错误。
- 页面重建后一次性事件不会重放。
总结
SwiftUI 数据流的核心是唯一所有权。局部值使用 State,外部可变值通过 Binding,引用模型由创建者使用 StateObject 持有。副作用离开 body,页面状态保持互斥,再按最低系统版本选择兼容能力,视图更新就会更可预测。
- 点赞
- 收藏
- 关注作者
评论(0)