SwiftUI における状態の伝播について
TODO: かなり雑なメモ。もうちょっと考える
SwiftUI にて、View 同士で状態を伝播したい際にどのような方式を取るべきか?考えてみる 例えば、子/孫 View から親 View に状態を伝播したい場合は多くあるはず
主に、以下のような方式があると思う
closure を利用する
Binding を利用する
(Flux 的な) 独自の状態共有/イベント伝播の仕組みを利用する
ViewModel & Delegate を利用する
気持ち
View の再利用性を高めたい
保守性は落とさずに開発速度は上げたい
考えてること
ロジックを持たない View であれば、closure や Binding で十分そう
ボタンのタップ等を SuperView に伝えれば十分な場合
SubView 側が複雑なロジックを必要とするような場合には、ロジックが切り出せている方が嬉しい
ViewModel として切り出すのも、Reducer として切り出すのもアリ
子/孫 View が増えていくと再利用可能な 親 View の initializer が肥大化する問題はある程度は仕方ない
そうなる設計自体が問題かもしれない
不要な状態やイベントは伝播せずに、必要に応じて隠蔽するべき
公開する必要のない状態のみ @State として、公開すべき状態は Store として公開する、みたいな方法もアリかもしれない
closure を利用する
SubView は状態を管理せず、イベントのみを SuperView に伝える
View の入れ子が増えていって伝播したいイベントが増えていくと、initializer が肥大化していく懸念がある
code:swift
struct SuperView: View {
var body: some View {
SubView {
// DO Something
}
}
}
code:swift
struct SubView: View {
let onTap: () -> Void
var body: some View {
Button {
onTap()
} label: {
Text("Press me")
}
}
}
Binding を利用する
SubView が状態を直接変更し、それが SwiftUI の Binding によって親 View にも伝播する 以下のような特徴がある
状態の変更を SubView 側で行う = 状態変更のロジックを SubView 側が持つことになる
SubView 側が状態を持つ = その状態を View に反映する
View の入れ子が増えていったときにどうする?問題は closure の場合と変わらない
code:swift
struct SuperView: View {
@State someState: Bool
var body: some View {
SubView($someState)
}
}
code:swift
struct SubView: View {
@Binding someState: Bool
var body: some View {
Button {
someState = !someState
} label: {
Text("Press me")
}
}
}
(Flux 的な) 独自の状態共有/イベント伝播の仕組みを利用する
複数の View で状態を共有して、イベントの伝播も Store 側で行う
どうすべきかな、と悩んだところ
単純に記述量が増えて面倒
これは仕組みで解決したい部分ではあるが...
State/Action の統合部分がうまく記述できると嬉しい...
SubView 向けに Store をビルドする部分をどう注入するか
詳細な実装はここでは考えないけど、イメージとしては以下のような感じ
code:text
+----------------+ +--------------+
| SuperViewStore | <-----> | SubViewStore |
+----------------+ +--------------+
^ ^
| |
v v
+-----------+ +---------+
| SuperView | | SubView |
+-----------+ +---------+
code:swift
struct SuperView: View {
@StateObject store: SuperViewStore
var body: some View {
let subStore = // ...
SubView(subStore)
}
}
code:swift
struct SubView: View {
@StateObject subStore: SubViewStore
var body: some View {
Button {
subStore.dispatch(.someAction)
} label: {
Text("Press me")
}
}
}
自分用チャート
SubView から SuperView に対しての伝播について、
SubView で発生したイベントが...
SubView に影響しない => closure を利用する
SubView に影響する
Binding だと厳しい?
厳しくない => binding を利用する
厳しい => 独自の伝播の仕組みを利用する
Binding だと厳しいの、どんな時かな... と言うのを考え直してみる