🔬

@Observable @State @Binding @Bindable 付ける付けない調査隊

2024/08/25に公開

毎年仕様が変わるので毎年やってますなw
機械的に付ける付けないをいろんなパターン試して挙動を調査するものです。
意味がない組み合わせがあっても気にしません。

やること

モデルを親Viewで作って子Viewに渡す。
イメージ図

モデル

@Observable
class Book {
    var title = String(Int.random(in: 0...100000))
}

@Observable を付ける付けないの2通り試します。
今回は class のみやります。

親View

struct ObservationTest1: View {
    @State var book = Book()
    var body: some View {
        Button { 
            book = Book()
        } label: { 
            Text("New")
        }
        Button { 
            book.title = String(Int.random(in: 0...100000))
        } label: { 
            Text("Change")
        }
        Text(book.title)
        //ObservationTest2(book: book)
        //ObservationTest3(book: $book)
        ObservationTest4(book: book)
    }

book@State を付ける付けないの2通り試します。

@State を付ける場合は

  • Newボタンあり

@State を付けない場合は

  • Newボタンなし(変更不可のエラーが出るのでなし)

共通して

  • book.titleを表示する
  • 子Viewを呼ぶ

子View

var book: Book が定義してあるObservationTest2。
@Binding var book: Book が定義してあるObservationTest3。
@Bindable var book: Book が定義してあるObservationTest4。

ObservationTest2

struct ObservationTest2: View {
    var book: Book
    var body: some View {
        Button { 
            book.title = String(Int.random(in: 0...100000))
        } label: { 
            Text("Change")
        }
        Text(book.title)
    }

ObservationTest2は

  • book.titleを変更するボタンがある
  • book.titleを表示する

ObservationTest3

struct ObservationTest3: View {
    @Binding var book: Book
    var body: some View {
        Button { 
            book = Book()
        } label: { 
            Text("New")
        }
        Button { 
            book.title = String(Int.random(in: 0...100000))
        } label: { 
            Text("Change")
        }
        Text(book.title)
    }

ObservationTest3は

  • bookを変更するボタンがある
  • book.titleを変更するボタンがある
  • book.titleを表示する

ObservationTest4

struct ObservationTest4: View {
    @Bindable var book: Book
    var body: some View {
        Button { 
            book.title = String(Int.random(in: 0...100000))
        } label: { 
            Text("Change")
        }
        Text(book.title)
    }
}

ObservationTest4は

  • book.titleを変更するボタンがある
  • book.titleを表示する

book への再代入は出来ないので、book の変更はありません。

@Observableなし @Stateなし @Bind〜なし

親で
titleを
更新すると
子で
titleを
更新すると
親Viewの
Text(book.title)は
更新なし 更新なし
子Viewの
Text(book.title)は
更新なし 更新なし

@Observableなし @Stateなし @Bindingあり

エラー
渡せない

@Observableなし @Stateなし @Bindableあり

エラー
@Bindable@Observable を要求

@Observableなし @Stateあり @Bind〜なし

親で
bookを
新規作成すると
親で
titleを
更新すると
子で
titleを
更新すると
親Viewの
Text(book.title)は
更新 更新なし 更新なし
子Viewの
Text(book.title)は
更新 更新なし 更新なし
親のbody再構築

@Observableなし @Stateあり @Bindingあり

親で
bookを
新規作成すると
親で
titleを
更新すると
子で
bookを
新規作成すると
子で
titleを
更新すると
親Viewの
Text(book.title)は
更新 更新なし 更新 更新なし
子Viewの
Text(book.title)は
更新 更新なし 更新 更新なし
親のbody再構築? 親のbody再構築?

@Observableなし @Stateあり @Bindableあり

エラー
@Bindable@Observable を要求

@Observableあり @Stateなし @Bind〜なし

親で
titleを
更新すると
子で
titleを
更新すると
親Viewの
Text(book.title)は
更新 更新
親Viewの
Text(book.title)は
更新 更新

@Observableあり @Stateなし @Bindingあり

エラー
渡せない

@Observableあり @Stateなし @Bindableあり

親で
titleを
更新すると
子で
titleを
更新すると
親Viewの
Text(book.title)は
更新 更新
子Viewの
Text(book.title)は
更新 更新

@Observableあり @Stateあり @Bind〜なし

親で
bookを
新規作成すると
親で
titleを
更新すると
子で
titleを
更新すると
親Viewの
Text(book.title)は
更新 更新 更新
子Viewの
Text(book.title)は
更新 更新 更新
親のbody再構築

@Observableあり @Stateあり @Bindingあり

親で
bookを
新規作成すると
親で
titleを
更新すると
子で
bookを
新規作成すると
子で
titleを
更新すると
親Viewの
Text(book.title)は
更新 更新 更新 更新
子Viewの
Text(book.title)は
更新 更新 更新 更新
親のbody再構築? 親のbody再構築?

@Observableあり @Stateあり @Bindableあり

親で
bookを
新規作成すると
親で
titleを
更新すると
子で
titleを
更新すると
親Viewの
Text(book.title)は
更新 更新 更新
子Viewの
Text(book.title)は
更新 更新 更新
親のbody再構築?

@State @Bindable が必要な理由

@Observable にすると title の変更に反応するので上の調査では @State@Bindable は要らないように見えるが、@State@Bindable はさらに下にBindingで渡すときに必要になる。

struct BookEditView: View {
    @Bindable var book: Book
    @Environment(\.dismiss) private var dismiss
    
    var body: some View {
        VStack() {
            HStack {
                Text("Title")
                TextField("Title", text: $book.title) //ここで必要
                    .textFieldStyle(.roundedBorder)
                    .onSubmit {
                        dismiss()
                    }
            }
            
            Button("Close") {
                dismiss()
            }
            .buttonStyle(.borderedProminent)
        }
        .padding()
    }
}

考察

上の調査では
@Observable あり @State あり @Binding ありと
@Observable あり @State あり @Bindable ありは、
インスタンスを新しく作成すること以外は一緒の結果。@Bindable は再代入しようとするとエラーが出る。これはわざとか?

@Binding よりも @Bindable が良い点としては @Observable の付け忘れでエラーが出る点がある。
@Observable なし @State あり @Binding ありはエラーが出ず、title を変更してもViewが更新されない。
@Observable なし @State あり @Bindable ありはエラーになる。@Bindable@Observable を要求するので。

さいごに

Observationの学習中に書いたもので理解が不十分かもしれません。何か指摘がある場合はよろしくお願いします。

Discussion