SwiftUI 应用的简单状态管理
凭借十多年来使用声明式 UI 框架大规模交付产品的经验,我们为 SwiftUI 提出了一种新的应用程序架构。 以 Flux 和 Redux 架构作为哲学上的先例,我们可以使用现代 Swift 并专门为现代 SwiftUI 设计一种架构。 这种架构鼓励声明式思维而不是命令式思维,函数式编程而不是面向对象编程,以及不可变模型值而不是可变模型对象。
该架构的核心是单向数据流
flowchart LR
accTitle: Data Flow in the ImmutableData Framework
accDescr: Data flows from action to state, and from state to view, in one direction only.
A[Action] --> B[State] --> C[View]
所有全局状态数据都按照这个基本模式流经应用程序,并且强制执行严格的关注点分离。 Actions 声明发生了什么,无论是用户输入、服务器响应还是设备传感器的变化,但它们对状态层或视图层一无所知。 状态层响应 action 描述的“新闻”,并相应地更新状态。 对状态进行更改的所有逻辑都包含在状态层中,但它对视图层一无所知。 然后,当新状态流经组件树时,视图响应状态层的变化。 然而,视图层对状态层一无所知。 通过保持这种严格的单向数据流和关注点分离,我们的应用程序代码变得更容易测试、更容易推理、更容易向新团队成员解释,并且在新功能需要时更容易更新。
此外,避免像双向数据绑定这样的复杂性,或者由可变性产生的意大利面条式代码,可以使我们的代码变得干净、快速且可维护。 这是此应用程序框架(及其背后的思想)与之前在 WWDC 上展示的其他 actions、状态和视图之间的关键区别。1 通过避免从状态层外部调用的直接突变,并拥抱不可变性,复杂性就会消失,我们的代码也会变得更加健壮。
ImmutableData 编程指南 是学习 ImmutableData
的权威参考。
ImmutableData
基础架构部署到以下平台
构建 ImmutableData
包需要 Xcode 16.0+ 和 macOS 14.5+。
如果您遇到任何兼容性问题,请提交 GitHub issue。
ImmutableData
包为您的产品提供了三个库模块
ImmutableData
:这是“数据”基础架构。 此模块构建用于管理全局应用程序状态的数据模型的基本类型。ImmutableUI
:这是“UI”基础架构。 此模块构建用于在您的 SwiftUI 组件图中显示和转换全局应用程序状态的基本类型。AsyncSequenceTestUtils
:此模块帮助我们为某些异步操作编写可预测和确定性的测试。 这不旨在作为依赖项发布到您的生产代码中 —— 这仅适用于测试代码。ImmutableData 编程指南提供了完整的示例应用程序产品教程。 这是学习如何使用 ImmutableData
构建产品的推荐方法。
一个非常基本的“Hello World”应用程序将是一个计数器:一个 SwiftUI 应用程序,用于递增和递减一个整数值。
我们从计数器应用程序的数据模型开始
typealias CounterState = Int
enum CounterAction {
case didTapIncrementButton
case didTapDecrementButton
}
enum CounterReducer {
@Sendable static func reduce(
state: CounterState,
action: CounterAction
) -> CounterState {
switch action {
case .didTapIncrementButton:
state + 1
case .didTapDecrementButton:
state - 1
}
}
}
我们定义一个 EnvironmentKey
值,它将保存一个 ImmutableData.Store
实例
import ImmutableData
import ImmutableUI
import SwiftUI
@MainActor struct StoreKey: @preconcurrency EnvironmentKey {
static let defaultValue = ImmutableData.Store<CounterState, CounterAction>(
initialState: 0,
reducer: { state, action in
fatalError("missing store")
}
)
}
extension EnvironmentValues {
var store: ImmutableData.Store<CounterState, CounterAction> {
get {
self[StoreKey.self]
}
set {
self[StoreKey.self] = newValue
}
}
}
由 StoreKey
返回的 defaultValue
使用一个 reducer 构建,该 reducer 会崩溃以指示程序员错误:我们的组件图应使用显式传递到环境的 stores。
我们现在可以构建一个 SwiftUI App
@main
@MainActor struct CounterApp {
@State var store = ImmutableData.Store(
initialState: 0,
reducer: CounterReducer.reduce
)
}
extension CounterApp: App {
var body: some Scene {
WindowGroup {
ImmutableUI.Provider(
\.store,
self.store
) {
CounterContent()
}
}
}
}
这是我们的 CounterContent
,用于显示和转换全局应用程序状态
@MainActor struct CounterContent {
@ImmutableUI.Selector(
\.store,
outputSelector: OutputSelector(
select: { $0 },
didChange: { $0 != $1 }
)
) var value
@ImmutableUI.Dispatcher(\.store) var dispatcher
}
extension CounterContent: View {
var body: some View {
VStack {
Button("Increment") {
self.didTapIncrementButton()
}
Text("Value: \(self.value)")
Button("Decrement") {
self.didTapDecrementButton()
}
}
}
}
这是可以分发的两个 action 值
extension CounterContent {
func didTapIncrementButton() {
do {
try self.dispatcher.dispatch(action: .didTapIncrementButton)
} catch {
print(error)
}
}
}
extension CounterContent {
func didTapDecrementButton() {
do {
try self.dispatcher.dispatch(action: .didTapDecrementButton)
} catch {
print(error)
}
}
}
Samples/Samples.xcworkspace
工作区包含三个从 ImmutableData
基础架构构建的示例应用程序产品。 ImmutableData 编程指南中讨论了这些产品。
版权所有 2024 Rick van Voorden 和 Bill Fisher
根据 Apache 许可证 2.0 版(“许可证”)获得许可;除非符合许可证的规定,否则您不得使用此文件。 您可以在以下网址获取许可证副本:
https://apache.ac.cn/licenses/LICENSE-2.0
除非适用法律要求或以书面形式同意,否则根据“按原样”基础分发的软件,不附带任何形式的明示或暗示的保证或条件。 有关权限和限制的特定语言,请参阅许可证。