在聊Redux之前,我們先回顧一下之前我們使用過的設(shè)計(jì)模式MVC,MVVM,MVP,VIPER。
如圖所示,由于這些設(shè)計(jì)模式是基于數(shù)據(jù)的流轉(zhuǎn)來定義,所以我們也可以把他們統(tǒng)稱為數(shù)據(jù)流框架。

用了這么多年的類MVC設(shè)計(jì)模式,我們發(fā)現(xiàn)了一些缺陷。
- Controller容易臃腫,尤其當(dāng)業(yè)務(wù)復(fù)雜的時(shí)候
- 耦合度高,測試性差。各層互相依賴,一個(gè)view依賴多個(gè)model,一個(gè)model 也會(huì)依賴多個(gè)view,一旦出現(xiàn)了bug,追蹤困難,上下文易丟失。
- 復(fù)用差
通過上邊問題的描述,我們反過來看怎么解決這個(gè)問題,所以我們希望的理想設(shè)計(jì)模式是什么樣的
- 各層依賴單一
- 耦合度低,上下文明確
- 復(fù)用強(qiáng)
試想如果我們把類MVC的這種數(shù)據(jù)的流轉(zhuǎn)模式,變成單向的,是不是就解決了這個(gè)問題。
為了解決這個(gè)問題,React里出現(xiàn)了一個(gè)有名的框架。
Redux
下面聊聊redux怎么解決這個(gè)問題。

構(gòu)成
- Store 包含三部分
Dispatch: 分發(fā)Action的方法
Reducer:去處理Action,更新store
State:狀態(tài) - Action:描述了行為的數(shù)據(jù)結(jié)構(gòu)。使用action描述所有變化帶來的好處是可以清晰的知道應(yīng)用發(fā)生了什么,如果應(yīng)用發(fā)生了變化,就知道為什么這么變,action就像發(fā)生變化的指示器
- Reducer:跟swift的函數(shù)reduce的思想類似,新狀態(tài)是由前一個(gè)狀態(tài)累加起來。
Action通過reducer觸發(fā)store的更新。
對(duì)于大應(yīng)用來說,不可能僅僅寫一個(gè)這樣的函數(shù),可以編寫多個(gè)小函數(shù)分別管理state的一部分。 - State: state只讀的,唯一改變state的方式就是觸發(fā)action
點(diǎn)擊view觸發(fā)一個(gè)action,通過dispatcher dispatch到reducer中處理,生成一個(gè)新的state,觸發(fā)store的更新,通知到view的更新
三個(gè)特性
- 單數(shù)據(jù)流模式
所有狀態(tài)存放在唯一的Store中,View 內(nèi)部也是盡量沒有自己的狀態(tài),當(dāng)Store中狀態(tài)變化,則View會(huì)進(jìn)行更新,當(dāng)View有用戶的操作,則Store就進(jìn)行更新。 所有狀態(tài)清晰明了,當(dāng)發(fā)現(xiàn)問題時(shí)候只需要檢查狀態(tài)就可以了。 - 可以預(yù)測性,(通過reducer的含義)
state + action = new state
state發(fā)生的任何變化,一定是有action引起的,保證了Redux應(yīng)用一定是會(huì)被追蹤的,一旦發(fā)現(xiàn)狀態(tài)發(fā)生了問題,一定是能找到對(duì)應(yīng)的action - 純函數(shù)更新Store
純函數(shù)的定義:函數(shù)的輸出結(jié)果完全取決于傳進(jìn)來的參數(shù),不依賴任何外部變量,不會(huì)產(chǎn)生副作用
特點(diǎn):容易測試
通過Redux的三個(gè)特性,對(duì)應(yīng)了上邊提到的解決了mvc架構(gòu)的痛點(diǎn)
Redux Demo
基于上邊的概念,我們用代碼實(shí)現(xiàn)一下單數(shù)據(jù)流
功能上簡單實(shí)現(xiàn)一個(gè)列表
- Action 描述具體的行為
enum MainPageAction {
case onClickRefresh
case refresh(list: [Classification])
}
- Store 接受view的發(fā)出的Action
class MainPageStore: ObservableObject {
private let reducer: Reducer
@Published public var state: MainPageState
init(state: MainPageState, reducer: @escaping Reducer) {
self.state = state
self.reducer = reducer
}
func dispatch(action: MainPageAction) {
if Thread.current.isMainThread {
self.state = self.reducer(action, state)
} else {
DispatchQueue.main.sync {
self.state = self.reducer(action, state)
}
}
}
}
extension MainPageState {
func classificationList() {
Task {
let list = await Classification.classifications()
mainPageStore.dispatch(action: .refresh(list: list))
}
}
}
- Reducer 處理Action
typealias Reducer = (MainPageAction, MainPageState) -> MainPageState
func mainPageReducer(action: MainPageAction, state: MainPageState) -> MainPageState {
var state = state
switch action {
case .onClickRefresh:
state.classificationList()
case .refresh(let list):
state.list = list
}
return state
}
- State 提供數(shù)據(jù),描述頁面狀態(tài)
struct MainPageState {
var list: [Classification] = []
}
- view SwiftUI展示并監(jiān)聽數(shù)據(jù)變化
let mainPageStore = MainPageStore(state: MainPageState(), reducer: mainPageReducer)
struct MainView: View {
@EnvironmentObject var store: MainPageStore
var body: some View {
NavigationView {
List {
ForEach(store.state.list) { item in
NavigationLink {
ClassificationList(item: item)
} label: {
MainPageCell(item: item)
}
}
}.navigationTitle(Text("Bookmarks"))
.navigationBarTitleDisplayMode(.automatic)
.toolbar {
Image(systemName: "arrow.clockwise").onTapGesture {
store.dispatch(action: .onClickRefresh)
}
}
}.onAppear {
store.dispatch(action: .onClickRefresh)
}
}
}
把上邊的代碼進(jìn)行優(yōu)化,抽象出Action協(xié)議,protocol Action {},Reducer,Store增加異步處理,參考框架ReSwift
https://github.com/lifexg/Blog
Blog應(yīng)用主要采用了Redux + SwiftUI + iCloudKit 技術(shù),可在APP Store搜書簽
功能點(diǎn)
- 書簽列表
- iCloud存儲(chǔ)同步數(shù)據(jù)
- Widget