扬庆の博客

SwiftUI 中 State 与 Binding 的使用边界

字数统计: 1.1k阅读时长: 4 min
2026/06/08 Share

在 SwiftUI 中, @State@Binding 都和视图状态有关, 但它们负责的事情不一样。

简单说:

  • @State 表示当前 View 自己拥有的一份状态
  • @Binding 表示当前 View 不拥有状态, 只是读写外部传进来的状态

如果把这个边界分清楚, 很多组件拆分时的数据流问题会简单很多。

@State: 状态由当前视图持有

当一个值只服务于当前视图, 并且不需要被父视图直接管理时, 可以使用 @State

1
2
3
4
5
6
7
8
9
10
11
12
13
struct CounterView: View {
@State private var count = 0

var body: some View {
VStack(spacing: 16) {
Text("count: \(count)")

Button("Add") {
count += 1
}
}
}
}

这里的 countCounterView 自己的内部状态。按钮点击后修改 count, SwiftUI 会重新计算 body, 页面随之刷新。

@State 通常适合这些场景:

  • 当前页面的临时 UI 状态, 例如展开/收起、选中项、输入框内容
  • 不需要被父级直接读取的局部状态
  • 视图销毁后可以自然丢弃的状态

需要注意的是, @State 一般声明为 private, 因为它代表当前视图自己的状态。如果外部需要控制它, 就应该考虑改成 @Binding

@Binding: 状态由外部持有

当子视图需要修改父视图的状态时, 子视图不应该再创建一份新的 @State, 而应该通过 @Binding 接收状态引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct ParentView: View {
@State private var isOn = false

var body: some View {
ToggleRow(isOn: $isOn)
}
}

struct ToggleRow: View {
@Binding var isOn: Bool

var body: some View {
Toggle("Enable", isOn: $isOn)
}
}

这里 isOn 的真实数据存放在 ParentView 里, ToggleRow 只是通过 @Binding 拿到读写入口。

传值时使用 $isOn, 表示传递的是绑定关系, 而不是普通的 Bool 值。

@Binding 通常适合这些场景:

  • 子视图需要修改父视图状态
  • 封装输入控件、开关控件、选择器等组件
  • 多个视图需要围绕同一份状态联动

常见误区: 子视图重新定义 State

有时候拆组件时, 容易在子视图里重新写一份 @State:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct ParentView: View {
@State private var name = "YangQing"

var body: some View {
NameEditor(name: name)
}
}

struct NameEditor: View {
@State var name: String

var body: some View {
TextField("Name", text: $name)
}
}

这段代码的问题是: NameEditor 里的 name 已经变成了子视图自己的状态。输入框修改后, 父视图里的 name 不会同步变化。

如果期望子视图修改父视图数据, 应该写成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct ParentView: View {
@State private var name = "YangQing"

var body: some View {
NameEditor(name: $name)
}
}

struct NameEditor: View {
@Binding var name: String

var body: some View {
TextField("Name", text: $name)
}
}

这样数据来源仍然只有一份, 子视图只是把修改动作传回父视图。

判断方式

可以用一个问题来判断:

这份状态到底归谁管理?

如果归当前视图管理, 用 @State

如果归父视图或外部管理, 当前视图只负责展示和修改, 用 @Binding

在组件封装中, 尽量让状态所有权保持清晰。谁创建状态, 谁负责决定状态的生命周期; 谁只是使用状态, 谁就通过绑定拿到读写能力。

一个简单封装例子

下面是一个可复用的搜索输入组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct SearchBar: View {
@Binding var text: String

var body: some View {
HStack {
Image(systemName: "magnifyingglass")

TextField("Search", text: $text)

if !text.isEmpty {
Button {
text = ""
} label: {
Image(systemName: "xmark.circle.fill")
}
}
}
.padding(12)
.background(Color.gray.opacity(0.12))
.clipShape(RoundedRectangle(cornerRadius: 10))
}
}

外部使用:

1
2
3
4
5
6
7
8
9
10
11
12
struct ContentView: View {
@State private var keyword = ""

var body: some View {
VStack {
SearchBar(text: $keyword)

Text("当前搜索: \(keyword)")
}
.padding()
}
}

SearchBar 不关心 keyword 的生命周期, 它只负责展示输入框和清空按钮。真正的数据由 ContentView 管理。

总结

@State@Binding 的区别, 本质是状态所有权的区别。

  • @State: 我有这份状态
  • @Binding: 我借用外部状态

写 SwiftUI 组件时, 先想清楚状态放在哪一层, 再决定用哪个属性包装器。这样页面拆分之后, 数据流会更稳定, 组件职责也会更清楚。

CATALOG
  1. 1. @State: 状态由当前视图持有
  2. 2. @Binding: 状态由外部持有
  3. 3. 常见误区: 子视图重新定义 State
  4. 4. 判断方式
  5. 5. 一个简单封装例子
  6. 6. 总结