このSwiftUIバインディング+状態の例は、bodyを再起動せずにどのように機能しますか?

Aug 19 2020

同僚が次のSwiftUIの例を思いついたのですが、これ期待どおりに機能するように見えます(テキストを入力すると、下にミラーリングされます)が、どのように機能するかは私には驚きです。

import SwiftUI

struct ContentView: View {
    @State var text = ""
    var body: some View {
        VStack {
            TextField("Change the string", text: $text) WrappedText(text: $text)
        }
    }
}

struct WrappedText: View {
    @Binding var text: String
    var body: some View {
        Text(text)
    }
}

SwiftUIの初心者のメンタルモデルでは、TextFieldに入力すると$textバインディングが変更され、@ State変数が変更されると思いましたtext。これにより、が無効ContentViewになり、の新しい呼び出しがトリガーされますbody。しかし、興味深いことに、それは起こりません!ContentViewにブレークポイントを設定すると、body1回だけヒットしますが、WrappedTextbodyは、バインディングが変更されるたびに実行されます。それでも、私が知る限り、text状態は本当に変化しています。

それで、ここで何が起こっているのですか?SwiftUIが変更のたびにContentViewの本体を再呼び出ししないのはなぜtextですか?

回答

1 Asperi Aug 20 2020 at 04:04

状態変更時に、SwiftUIレンダリングエンジンは最初にbody内のビューが等しいかどうかをチェックし、それらの一部が等しくない場合は、bodyを呼び出して再構築しますが、等しくないビューのみを呼び出します。あなたの場合、1つのビューが(値として)値に依存しないためtext(バインドは参照のようなものです-同じです)、このレベルで再構築するものはありません。しかし、内部WrappedTextではText、新しいtextものと古いものが同じではないことが検出されたtextためWrappedText、この部分を再レンダリングするためにの本体が呼び出されます。

これは、SwiftUIのレンダリング最適化として宣言されています-正確に変更されたビューを同等にチェックおよび検証することによって。

デフォルトでは、このメカニズムはビューの構造体プロパティによって機能しますが、Eqatableプロトコルに対するビューを確認し、ビューを.equatable()再レンダリングする必要があるかどうかを検出するためのより複雑なロジックを提供するために修飾子をマークすることで、このメカニズムに関与できます。