Travis CI | |
框架 | |
平台 | |
许可证 |
关于
RxReduce 是状态容器模式(如 Redux)的响应式实现。它基于状态不可变和单向数据流的概念。
架构问题
这几年来,关于将其他架构模式应用于移动应用的博客文章、教程、书籍、研讨会非常多。所有这些模式背后的理念都是为了提供更好的方法来
- 满足 SOLID 要求(维基百科)
- 通过设计来产生更安全的代码
- 让我们的代码更容易测试
古老的老式 MVC 越来越被 MVP、MVVM 或 VIPER 取代。这里我不会详细介绍这些模式,因为它们已经得到了很好的文档记录。我认为 MVVM 目前是最流行的模式,这主要是因为它与 MVC 和 MVP 的相似性,以及它利用数据绑定来简化数据流程的能力。此外,它很容易通过 Coordinator 模式和响应式编程来增强。
如果您对响应式 Coordinators 感兴趣,可以查看这个项目(RxFlow)
话虽如此,至少还有另一种架构模式脱颖而出:状态容器。
其中一个最著名的例子是 Redux,但让我们不要局限于特定的实现。
关于状态容器的一些资源:
该模式的主要目标是
- 在您的应用程序中展示清晰/可重复的数据流
- 依赖于单一事实来源:即 状态
- 利用值类型来处理状态的不可变性
- 推广函数式编程,因为唯一改变状态的方式是应用一个自由函数:reducer(还原器)
相较于更传统的做法,我认为这种方法非常有趣,因为它负责维护应用状态的一致性。MVC,MVP,MVVM或VIPER可以帮助将你的应用程序切片成定义良好的层,但它们在处理应用程序状态方面并没有那么指导。
响应式编程是状态容器架构的绝佳伴侣,因为它可以帮助
- 传播状态修改
- 构建异步操作以修改状态(用于网络、持久性等)
RxReduce
RxReduce
- 提供了一個通用的存储库,可以处理各种类型的状态
- 通过响应式机制公开状态修改
- 提供了一个简单/统一的通过动作(Actions)以同步和异步方式修改状态的方法
安装
使用Carthage
在你的Cartfile文件中
github "RxSwiftCommunity/RxReduce"
使用CocoaPods
在你的Podfile文件中
pod 'RxReduce'
关键原则
RxReduce的核心机制非常直观
以下是一个动画,解释了状态容器架构内的流程
- Store(存储库)是处理你状态的组件。它只有一个输入:一个函数“dispatch()”,它接收一个参数:一个动作(Action)。
- 唯一触发状态修改的方式就是调用这个“dispatch()”函数。
- 动作(Actions)是没有业务逻辑的简单类型。它们封装了修改状态所必需的有效负载。
- 只有称为 Reducer (RxReduce !)的免费和可测试的函数才能改变 State。一个 “reduce()” 函数接受一个 State,一个 Action,并返回一个改变的 State... 这就这么简单。更精确地说,一个 reducer 返回 State 的一个改变过的子状态。实际上,每个 State 的子状态都有一个 reducer。我们所说的子状态指的是组成 State 的所有属性。
- Store 将确保您针对每个子状态只提供一个且只有一个 reducer。这为您应用逻辑带来了安全性和一致性。每个 reducer 都有一个明确的范围。
- Reducers 不能 执行异步逻辑,它们只能以同步和可读的方式改变状态。异步作业将由 Reactive Actions 处理。
- 您可以通过 Store 提供的 Observable<State> 接收状态变化的通知。
如何使用 RxReduce
代码示例
如何声明 State
由于状态容器的核心思想是关于不可变性,避免引用类型的不受控传播和竞争条件,因此 State 必须是值类型。结构体和枚举非常适合此用途。
struct TestState: Equatable {
var counterState: CounterState
var userState: UserState
}
enum CounterState: Equatable {
case empty
case increasing (Int)
case decreasing (Int)
}
enum UserState: Equatable {
case loggedIn (name: String)
case loggedOut
}
使状态 Equatable 不是强制性的,但这将允许 Store 在两次动作之间没有变化时不要发射新的状态值。因此,我强烈建议遵循 Equatable,以最小化视图刷新的次数。
如何声明 Actions
动作是简单的数据类型,它封装了在 reducers 中用于改变状态的负载。
enum AppAction: Action {
case increase(increment: Int)
case decrease(decrement: Int)
case logUser(user: String)
case clear
}
如何声明 Reducer
正如我说的,Reducer 是一个免费函数。这类函数接受一个值,返回一个幂等值,并且不会执行任何副作用。它们的声明与类型定义无关。这非常方便进行测试
这里我们定义了两个 Reducer,将处理它们各自专属的子状态。第一个修改 CounterState,第二个修改 UserState。
func counterReduce (state: TestState, action: Action) -> CounterState {
guard let action = action as? AppAction else { return state.counterState }
var currentCounter = 0
// we extract the current counter value from the current state
switch state.counterState {
case .decreasing(let counter), .increasing(let counter):
currentCounter = counter
default:
currentCounter = 0
}
// according to the action we mutate the counter state
switch action {
case .increase(let increment):
return .increasing(currentCounter+increment)
case .decrease(let decrement):
return .decreasing(currentCounter-decrement)
case .clear:
return .empty
default:
return state.counterState
}
}
func userReduce (state: TestState, action: Action) -> UserState {
guard let action = action as? AppAction else { return state.userState }
// according to the action we mutate the users state
switch action {
case .logUser(let user):
return .loggedIn(name: user)
case .clear:
return .loggedOut
default:
return state.userState
}
}
这些 Reducer 将只处理它们负责的 Action,不多也不少。
如何声明一个 Store
RxReduce 提供了一个通用的 Store,可以处理应用程序的状态。您只需提供一个初始状态
let store = Store<TestState>(withState: TestState(counterState: .empty, userState: .loggedOut))
如何合并子状态突变到一个整体状态
正如我们所见:一个 Reducer 只负责其专属的子状态。然后,我们需要定义一系列 Reducer 来处理整个应用程序的状态突变。所以,我们需要一种机制将所有突变子状态组装成一个一致的状态。
我们将使用函数式编程技术来实现这一点。
透镜
透镜是函数式编程中访问和修改值类型的一种通用方式。这是告诉 Store 如何修改某个子状态的操作。例如,CounterState 的透镜将是
let counterLens = Lens<TestState, CounterState> (get: { testState in return testState.counterState },
set: { (testState, counterState) -> TestState in
var mutableTestState = testState
mutableTestState.counterState = counterState
return mutableTestState
})
这是定义如何访问状态中的 CounterState 属性(get 闭包)以及如何修改它(set 闭包)。
变异者
变异者简单地说是将一个Reducer和子状态的一个Lens分组在一起的结构。对于CounterState来说也是一样。
let counterMutator = Mutator<TestState, CounterState>(lens: counterLens, reducer: counterReduce)
变异者包含了所有关于如何变异CounterState和如何将其设置为父状态所需的一切信息。
让我们将所有部件组合在一起
实例化商店之后,您必须注册所有将处理状态子状态的变异者。
let store = Store<TestState>(withState: TestState(counterState: .empty, userState: .loggedOut))
let counterMutator = Mutator<TestState, CounterState>(lens: counterLens, reducer: counterReduce)
let userMutator = Mutator<TestState, UserState>(lens: userLens, reducer: userReduce)
store.register(mutator: counterMutator)
store.register(mutator: userMutator)
现在让我们变异状态
store.dispatch(action: AppAction.increase(increment: 10)).subscribe(onNext: { testState in
print ("New State \(testState)")
}).disposed(by: self.disposeBag)
等等,还有更多的东西 …
动作列表
最近,Swift 4.1 引入了条件一致性。如果您对这个概念不熟悉:条件一致性的概述。
基本上,它允许仅在关联的内部类型也符合该协议的情况下,使泛型类型符合协议。
例如,RxReduce利用这个特性来使动作数组成为动作!这样做之后,向商店发送动作列表就像这样发送是完全可行的
let actions: [Action] = [AppAction.increase(increment: 10), AppAction.decrease(decrement: 5)]
store.dispatch(action: actions).subscribe ...
数组中声明的动作将按顺序执行
异步性
将动作数组成为动作本身是巧妙的,但是因为我们在使用响应式编程,RxReduxe也应用了这种技术到 RxSwift Observables。它提供了一种非常优雅的方式来向商店发送
let increaseAction = Observable<Int>.interval(1, scheduler: MainScheduler.instance).map { _ in AppAction.increase(increment: 1) }
store.dispatch(action: increaseAction).subscribe ...
这将每1秒触发一个 AppAction.increase 动作,并相应地变异状态。
如果您想将RxReduce与Redux进行比较,这种执行异步操作的能力与“Action Creator”概念相当。
记录在案,我们甚至可以将一个包含Observable
let increaseAction = Observable<Int>.interval(1, scheduler: MainScheduler.instance).map { _ in AppAction.increase(increment: 1) }
let decreaseAction = Observable<Int>.interval(1, scheduler: MainScheduler.instance).map { _ in AppAction.decrease(decrement: 1) }
let asyncActions: [Action] = [increaseAction, decreaseAction]
store.dispatch(action: asyncActions).subscribe ...
条件合规性是一个非常强大的特性。
还有一件事
Store提供了一种从任何地方“观察”状态变更的方法。您只需要订阅“state”属性即可。
store.state.subscribe(onNext: { appState in
print (appState)
}).disposed(by: self.disposeBag)
演示应用
提供了一个演示应用来展示核心机制,例如异步性、子状态以及视图状态渲染。
工具和依赖
RxReduce依赖于
- SwiftLint进行静态代码分析(《https://github.com/realm/SwiftLint》)
- RxSwift将状态和操作作为Observables暴露给您的应用程序和Store,以便它们可以做出反应(《https://github.com/ReactiveX/RxSwift》)
- 在演示应用程序中使用Reusable以简化Storyboard分割到原子ViewController(《https://github.com/AliSoftware/Reusable》)