🧞
TCAでのStateの初期化はinitでデフォルト引数を使うと便利
はじめに
TheComposableArchitectureでのiOSアプリ開発についてアドバイスをする仕事をしていると、「Stateの初期化はどうするんだっぴ?」という質問を頻繁に受けるので書いておきます。
結論: initのデフォルト引数で初期化のパラメータを与えると便利
参考にisowordsでのState初期化例を示します
利用時は下記
AppReducer.State()
// もしくはたいてい.init()でいい
たいてい利用時は型を推論できて省略できるので.init()
でいい。
struct AppView_Previews: PreviewProvider {
static var previews: some View {
AppView(
store: .init(
initialState: .init(), // これ!
reducer: AppReducer()
)
)
}
理由
- SwiftUIプレビュー時にパラメータを変化させたい
- initでパラメータを指定できるようにすればいい
- Stateのinitは書く回数が多く、Stateのプロパティは仕様の変更や設計の方針変更によって増減しやすい
- 変更のたびにStateのinit呼び出し側を変更したくない
- デフォルト引数を使えばいい
- 変更のたびにStateのinit呼び出し側を変更したくない
あんまりよくない例
あえて変な例を見せます
自身で初期化しない例
あえて面倒な例ですが、次のようにやると呼び出し側が初期化する必要があってさらに引数も省略されていないのですっごい面倒です。
public struct AppReducer: ReducerProtocol {
public struct State: Equatable {
public var game: Game.State?
public var onboarding: Onboarding.State?
public var home: Home.State
public init(
game: Game.State?,
home: Home.State,
onboarding: Onboarding.State?
) {
self.game = game
self.home = home
self.onboarding = onboarding
}
initで初期化するのにプロパティの宣言時に値をセットするのは無駄だし冗長
プロパティの宣言時に代入してさらにinitでデフォルト値を設定する例を示してみる。
public struct AppReducer: ReducerProtocol {
public struct State: Equatable {
public var game: Game.State? = nil // initでやってるので無駄
public var onboarding: Onboarding.State? = .init() // これも無駄というか...
public var home: Home.State = .init() // 無駄
public init(
game: Game.State? = nil,
home: Home.State = .init(),
onboarding: Onboarding.State? = nil
) {
self.game = game
self.home = home
self.onboarding = onboarding
}
無駄でもあるし、さらに問題になるのがそもそも設計としてこのAppReducerのStateが初期化されるとき、結局Onboading.State
プロパティの値はnilになるべきなのに初期値で.init()
してしまっているので無駄っていうかコストがかかって冗長です。
initで初期化しない例
TCAのTicTackToeというサンプルですがこんなのがあります。これをやるとプレビュー時にinitでプロパティの値を変更するパターンを試すのがちょっとだけ億劫です。
public struct Login: ReducerProtocol, Sendable {
public struct State: Equatable {
public var alert: AlertState<Action>?
public var email = ""
public var isFormValid = false
public var isLoginRequestInFlight = false
public var password = ""
public var twoFactor: TwoFactor.State?
public init() {}
}
TCAはサンプルコードが充実してますが、それはベストプラクティスではない場合もあります。
Discussion