このSwiftUIバインディング+状態の例は、bodyを再起動せずにどのように機能しますか?
同僚が次の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にブレークポイントを設定すると、body
1回だけヒットしますが、WrappedTextbody
は、バインディングが変更されるたびに実行されます。それでも、私が知る限り、text
状態は本当に変化しています。
それで、ここで何が起こっているのですか?SwiftUIが変更のたびにContentViewの本体を再呼び出ししないのはなぜtext
ですか?
回答
状態変更時に、SwiftUIレンダリングエンジンは最初にbody内のビューが等しいかどうかをチェックし、それらの一部が等しくない場合は、bodyを呼び出して再構築しますが、等しくないビューのみを呼び出します。あなたの場合、1つのビューが(値として)値に依存しないためtext
(バインドは参照のようなものです-同じです)、このレベルで再構築するものはありません。しかし、内部WrappedText
ではText
、新しいtext
ものと古いものが同じではないことが検出されたtext
ためWrappedText
、この部分を再レンダリングするためにの本体が呼び出されます。
これは、SwiftUIのレンダリング最適化として宣言されています-正確に変更されたビューを同等にチェックおよび検証することによって。
デフォルトでは、このメカニズムはビューの構造体プロパティによって機能しますが、Eqatable
プロトコルに対するビューを確認し、ビューを.equatable()
再レンダリングする必要があるかどうかを検出するためのより複雑なロジックを提供するために修飾子をマークすることで、このメカニズムに関与できます。