概要
Reduxアーキテクチャでカウンターアプリを作ります。
Reduxを体験することが目的です。
動作は下のような感じです。
全体のソースコードはこちらです。
Reduxアーキテクチャの登場人物
Reduxの登場人物を紹介します。
View
画面表示・ユーザーイベントをハンドリングします。
State
アプリケーションの状態を管理します。
Action
Stateをどのように変更するか表現します。
Reducer
StateとActionから新たなStateを生み出します。
Store
StateとReducerを内包します。
Actionのディスパッチを担当します。
Reduxの流れ
主に以下の流れでアプリケーションの状態を更新します。
1. Viewでイベントが発生する
2. イベントをトリガーにStore
がAction
をディスパッチする
3. Reducer
がディスパッチされたAction
と既存のState
から新しいState
を生成する
4. State
を監視しているView
が更新される
カウンターアプリを作る
Reduxを使った実装を体験してみます。
プロジェクトを作成する
Xcodeで新規プロジェクトを作ります。
その後、Swift Package MangerでReSwiftをインストールします。
State
を実装する
カウンターアプリなので数字を状態管理します。
struct CounterState {
var number: Int = 0
}
Action
を実装する
数字を一つ増やすAction
と数字を一つ減らすAction
を準備します。
extension CounterState {
enum Action: ReSwift.Action {
case increment
case decrement
}
}
Reducer
を実装する
Action
と既存のState
から新しいState
を生成します。
extension CounterState {
...
static func reducer(action: ReSwift.Action, state: CounterState?) -> CounterState {
var state = state ?? CounterState()
guard let action = action as? CounterState.Action else { return state }
switch action {
case .increment:
state.number += 1
case .decrement:
state.number -= 1
}
return state
}
}
State
を束ねるAppState
を実装する
State
のルートに位置するState
を実装します。
AppState
と名づけることが多いです。
struct AppState {
var counterState = CounterState()
}
AppState
と対応するReducer
を実装する
AppState
に対応するReducer
を実装します。
発生したAction
を下位のReducer
に渡します。
今回の場合は appReducer -> CounterState.reducer
となります。
func appReducer(action: ReSwift.Action, state: AppState?) -> AppState {
var state = state ?? AppState()
state.counterState = CounterState.reducer(action: action, state: state.counterState)
return state
}
Store
を実装する
Store
は以下の特徴を持ちます。
1. アプリケーションに一つ
2. State
, Reducer
を保持する
3. Action
のディスパッチを担当する
AppDelegate
で生成しましょう。
import UIKit
import ReSwift
let appStore = ReSwift.Store<AppState>(reducer: appReducer, state: nil)
...
ボタンタップ時にAction
をディスパッチする
ボタンタップ時にディスパッチして、新しいState
が生成されるようにします。
また、Store
がViewのイベントを監視できるようにします。
import UIKit
import ReSwift
class ViewController: UIViewController, StoreSubscriber {
@IBOutlet weak var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
appStore.subscribe(self)
}
func newState(state: AppState) {
label.text = String(state.counterState.number)
}
@IBAction func tapCountUpButton(_ sender: UIButton) {
appStore.dispatchFunction(CounterState.Action.increment)
}
@IBAction func tapCountDownbutton(_ sender: UIButton) {
appStore.dispatch(CounterState.Action.decrement)
}
}
Reduxのデータフローを実現できました。
1. Viewでイベントが発生する
2. StoreがActionをディスパッチする
3. ReducerがActionを解析して、新しいStateを生成する
4. 新しいStateを元にViewが更新される
ソースコードはこちらです。