Cloe
Cloe是针对SwiftUI的Redux在Combine上实现的解决方案,具有良好的设计风格。
设置您的store
struct AppState {
var appName = "Demo App"
var age = 6
var names = ["hank", "cloe", "spike", "joffrey", "fido", "kahlil", "malik"]
static let initialValue = AppState()
}
enum AppAction: Action {
case growup
}
typealias AppStore = Store<AppReducer>设置您的reducer
func appReducer(state: inout AppState, action: Action) {
guard let action = action as? AppAction else { return }
switch action {
case .growup:
state.age += 1
}
}实例化您的Store
// Create a store with the publisher middleware
// this middleware allows us to use `PublisherAction`
// later to dispatch an async action.
let store = AppStore(
reducer: appReducer,
state: .initialValue,
middlewares: [createPublisherMiddleware()])
// Inject the store with `.environmentObject()`.
// Alternatively we could inject it with `.environment()`
let contentView = ContentView().environmentObject(store)
// later...
window.rootViewController = UIHostingController(rootView: contentView)(可选) 向store中添加一些便利扩展
这些扩展可以提高与store工作的便捷性。使用内建的 dispatch 函数,我们通常会使用 store.dispatch(AppAction.growup) 来发送。有了这个 dispatch 扩展,我们可以使用 store.dispatch(.growup) 来替代。
subscript 扩展允许我们在SwiftUI视图中避免使用闭包。例如,按钮可以使用以下方式实现:Button("Grow up", action: store[.growup])。
extension Store {
func dispatch(_ action: AppAction) {
dispatch(action as Action)
}
subscript(_ action: AppAction) -> (() -> Void) {
{ [weak self] in self?.dispatch(action as Action) }
}
}将您的 SwiftUI 视图连接到您的 store
这是一个使用状态选择器注入状态的示例。我们在 View 中定义了状态选择器,但也可以在任何地方定义。
struct MyView: View {
var index: Int
// Define your derived state
struct MyDerivedState: Equatable {
var age: Int
var name: String
}
// Inject your store
@EnvironmentObject var store: AppStore
// Connect to the store
var body: some View {
Connect(store: store, selector: selector, content: body)
}
// Render something using the selected state
private func body(_ state: MyDerivedState) -> some View {
Text("Hello \(state.name)!")
}
// Setup a state selector
private func selector(_ state: AppState) -> MyDerivedState {
.init(age: state.age, name: state.names[index])
}
}如果您想连接到 store 的状态而不定义选择器,则使用 ConnectStore。请注意,目前 ConnectStore 不会像 Connect 那样跳过重复的状态。
派发一个简单的动作
下面是如何派发一个简单动作的示例
Button("Grow up") { self.store.dispatch(AppAction.growup) }
// ... or ...
Button("Grow up", action: store[AppAction.growup])或者使用上面提到的可选的 Store 扩展
Button("Grow up") { self.store.dispatch(.growup) }
// ...or...
Button("Grow up", action: store[.growup])使用 publisher 中间件派发异步动作
下面是一个简单示例,更多关于 publisher 中间件的信息请参阅 此处。
Button("Grow up") { self.store.dispatch(self.delayedGrowup) }
//...
private let delayedGrowup = PublisherAction<AppState> { dispatch, getState, cancellables in
Just(())
.delay(for: 2, scheduler: RunLoop.main)
.sink { _ in
dispatch(AppAction.growup)
}
.store(in: &cancellables)
}使用 publisher-dispatcher 跟踪异步任务进度
与 ReSwift 有何不同?
- ReSwift 经受战火考验。
- ReSwift 正在实际的生产应用中使用。
- Cloe 使用 Combine Publishers 而不是 定制的 StoreSubscriber
- Cloe 的 Middleware 比的 ReSwift Middleware 简单,但实现了相同级别的灵活性。
- Cloe的
combineMiddleware函数更简洁、更容易阅读。 - Cloe提供了一种流畅的方式来连接您的SwiftUI视图。
- Cloe未为主Store状态提供跳过重复项的选项,但当您将其连接到SwiftUI组件时,它总是会跳过重复的状态(可能有所变化)。
为什么Store对象要遵守ObservableObject协议?
您可能已经注意到Cloe的Store类遵守了ObservableObject协议。然而,Store并不包含任何@Published属性。添加这种遵守只是为了方便使用.environmentObject()向您的store注入。然而,由于我们不暴露任何@Published变量,请不要期望当store发生变化时,带有
@ObservedObject var store: AppStore的视图会自动重新渲染。这种设计是故意的,这样您就可以使用订阅通过Connect的更细粒度的更新。
示例
要运行示例项目,请克隆此存储库,然后从iOS Example目录打开iOS Example.xcworkspace。
要求
- iOS 13
- macOS 10.15
- watchOS 6
- tvOS 13
安装
使用Swift包管理器将此添加到您的项目中。在Xcode中,这很简单:File > Swift Packages > Add Package Dependency... 就完成了。以下是针对遗留项目的其他安装选项。
CocoaPods
如果你已经在使用 CocoaPods,只需将 'Cloe' 添加到你的 Podfile 中,然后运行 pod install。
Carthage
如果你已经在使用 Carthage,只需将其添加到你的 Cartfile
github "gilbox/Cloe" ~> 0.3.0
然后运行 carthage update 来构建框架,并将构建的 Cloe.framework 拖入你的 Xcode 项目中。
许可证
Cloe 采用 MIT 许可证。更多详细信息请参阅 LICENSE 文件。