先理解 @Binding 解决什么问题
用 @State 的时候,状态归属于某一个 View。但子 View 怎么修改父 View 的状态?
struct ParentView: View {
@State var isOn: Bool = false
var body: some View {
ToggleView(isOn: isOn) // ❌ 子 View 拿到的只是一个值的拷贝
}
}
你把 isOn 传给子 View,子 View 改了它自己的拷贝,父 View 毫不知情,UI 也不会更新。
@Binding 就是用来解决这个问题的
@Binding 不是一份数据的拷贝,而是一条双向通道,指向原始数据的存储位置。
读它,读的是原始值;写它,写的是原始存储,父 View 会同步感知并刷新。
// 父 View:状态归属于这里
struct ParentView: View {
@State var isOn: Bool = false
var body: some View {
// 用 $ 前缀把 @State 转成 Binding 传下去
ToggleView(isOn: $isOn)
}
}
// 子 View:不拥有状态,只拿到一条"通道"
struct ToggleView: View {
@Binding var isOn: Bool // 声明为 Binding,表示"我不拥有这个数据"
var body: some View {
Button("切换") {
isOn.toggle() // 写的是父 View 里的原始 @State,触发父 View 刷新
}
}
}
@Binding 的本质
@propertyWrapper
public struct Binding<Value> {
// 你平时用 isOn 读写的就是这个
public var wrappedValue: Value { get nonmutating set }
// 你用 $isOn 拿到的还是 Binding 自身,可以继续往下传
public var projectedValue: Binding<Value> { get }
}
@Binding 内部存的不是值本身,而是一对 getter + setter 闭包,分别指向上层 @State(或其他数据源)的读写操作。所以写 isOn = true 时,实际上是调用了那个 setter 闭包,最终改变的是父 View 的 @State。
使用 @Binding 时需要关心的问题
-
数据归属权问题:
@Binding的原则是"我不拥有数据,我只是一个读写通道"。如果一个 View 需要拥有状态,用@State;如果只是借用和修改上层的状态,用@Binding。 -
单向来源原则(Single Source of Truth):一条
@Binding链条最终必须溯源到某个真实的数据存储(比如@State、@StateObject中的属性),不要出现 Binding 套 Binding 套 Binding 的迷宫,链条越短越清晰。 -
$符号的含义:$isOn拿到的是projectedValue,对@State来说它是一个Binding<Bool>,这就是为什么父 View 传$isOn,而子 View 声明@Binding var isOn,类型是对得上的。 -
不要在 body 外部调用:和
@State一样,对@Binding属性的读写应发生在body或body调用的方法中,以确保 SwiftUI 能正确追踪依赖。