SwiftUI 中的全局 AppState 架构
Source: Dev.to
请提供您希望翻译的正文内容,我将为您翻译成简体中文并保留原始的 Markdown 格式和技术术语。
介绍
随着 SwiftUI 应用的增长,一个常见的问题随之而来:这个状态到底存放在哪里?
典型的全局关注点包括:
- 认证状态
- 选中的标签页
- 深度链接
- 引导流程
- 全局错误
- 应用生命周期事件
- 功能协同
将这些状态散落在各个视图和视图模型中会导致导航不可预测、逻辑重复、难以调试的 bug,以及状态在错误的时机被重置。
解决方案是 Global AppState ——一种清晰、可扩展的架构,避免将应用变成巨大的上帝对象。
什么应放在 AppState 中
AppState 表示全局、跨功能的状态,具备以下特性:
- 在单个页面销毁后仍然存在
- 协调多个功能
- 响应生命周期变化
- 驱动导航
- 在视图重新创建后仍然保持
典型示例:
- 用户会话 / 认证状态
- 已选标签页
- 全局导航路由
- 深度链接处理
- 应用阶段(前台/后台)
- 全局加载 / 同步标志
- 全局错误展示
经验法则
- 如果多个功能都需要它 → AppState
- 如果只有单个页面需要它 → ViewModel
什么不应该放在 AppState 中
- 文本字段的值
- 滚动位置
- 本地动画
- 特定功能的数据列表
- 临时 UI 切换
使用 @Observable 定义 AppState
@Observable
class AppState {
var session: UserSession?
var selectedTab: Tab = .home
var route: AppRoute?
var isSyncing = false
var pendingDeepLink: DeepLink?
}
该对象在应用的整个生命周期内存在,仅注入一次,并驱动高层行为。
注入 AppState
@main
struct MyApp: App {
@State private var appState = AppState()
var body: some Scene {
WindowGroup {
RootView()
.environment(appState)
}
}
}
在视图中访问 AppState
@Environment(AppState.self) var appState
不需要单例。
通过 AppState 进行导航
enum AppRoute: Hashable {
case login
case home
case profile(id: String)
}
在根视图中:
.navigationDestination(for: AppRoute.self) { route in
switch route {
case .login:
LoginView()
case .home:
HomeView()
case .profile(let id):
ProfileView(userID: id)
}
}
通过更新状态触发导航:
appState.route = .profile(id: user.id)
优势
- 可预测的
- 可测试的
- 支持深度链接
集中式深链接处理
func handle(_ link: DeepLink) {
switch link {
case .profile(let id):
route = .profile(id: id)
case .home:
selectedTab = .home
}
}
视图不再解析 URL;AppState 将外部意图转换为内部状态。
响应生命周期变化
@Environment(\.scenePhase) var phase
.onChange(of: phase) { newPhase in
switch newPhase {
case .background:
saveState()
case .active:
refreshIfNeeded()
default:
break
}
}
这将生命周期逻辑从视图和视图模型中分离出来。
避免让 AppState 过载
错误示例
class AppState {
var feedPosts: [Post]
var profileViewModel: ProfileViewModel
var searchText: String
}
正确做法
AppState协调。- 特定功能的视图模型拥有自己的数据。
- 服务处理副作用。
使用子状态扩展 AppState
@Observable
class AppState {
var sessionState = SessionState()
var navigationState = NavigationState()
var syncState = SyncState()
}
每个子状态都有明确的职责和有限的表面区域,在大型应用中能够优雅地扩展。
测试 AppState
func test_navigation_changes_route() {
let appState = AppState()
appState.route = .login
XCTAssertEqual(appState.route, .login)
}
func test_profile_deep_link() {
let appState = AppState()
appState.handle(.profile(id: "123"))
XCTAssertEqual(appState.route, .profile(id: "123"))
}
不涉及 UI —— 测试专注于纯数据。
状态流
用户操作
↓
视图模型
↓
应用状态(如果是全局的)
↓
视图响应
设计良好的全局 AppState 的好处
- 简化导航
- 稳定生命周期行为
- 使深层链接变得简单
- 消除全局 hack
- 提升可测试性
- 在团队之间可扩展
在适度使用的情况下,全局 AppState 是 SwiftUI 中最强大的架构工具之一。