简介

文章将简要讲述何时使用@State, @Binding, @StateObject, @ObservedObject, @EnvironmentObject。

@State

@State属性包裹器可用于当你的视图对象响应任何状态改变时候。换句话说,视图初始化@State修饰的属性变量。该属性变量的变更只发生在内部View,而不被外界所改变。

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

var body: some View {
VStack {
Text("intValue equals \(intValue)")

Button("Increment") {
intValue += 1
}
}
}
}

在内部,SwiftUI将会存储 @State修饰的属性值,然后贯穿整个渲染或重复渲染的生命周期中将持久化存储这个值,当在视图刷新或者重新创建的时候,该属性值可以很好地被视图自身管理。当然,你也可以用private修饰词修饰@State属性,保证只在内部修改@State属性。
@State使用场景:

  • 在被修饰的属性需要响应你视图变化的状态时候
  • 你修饰的是一个值类型(结构体、枚举)

@Binding

@Binding属性包裹器修饰的属性值被其他View所传递。视图内定义的@Binding修饰的属性值可接受外部传入的值,也包含写的权限,即可对Binding修饰的属性做更改,可将值响应回到父容器。(一般在子view Binding修饰属性接收父容器初始值,然后子view做更改,父视图可实时响应其结果。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct StateView: View {
@State private var intValue = 0

var body: some View {
VStack {
Text("intValue equals \(intValue)")

BindingView(intValue: $intValue)
}
}
}

struct BindingView: View {
@Binding var intValue: Int

var body: some View {
Button("Increment") {
intValue += 1
}
}
}

说明:以上代码StateView中把 @State包裹修饰的intValue通过$符号传递到BindingView中。子View通过Binding形式间接修改了@State包裹的intValue。由于@Binding包裹修饰时,值总是从外部传递进来。所以当试图被丢弃时,SwiftUI不管理Binding。 @Bindingg可以比作是绑定传递、响应父视图,而@State是需要持久化状态变更。

@Binding使用场景:

  • 你需要对父视图的属性进行读或写权限时候
  • 被包裹的属性是值类型的时候(结构体或枚举)

@StateObject

@StateObject修饰属性有点类似@State的用法,但可用来引用类型比如class. 一般用于修饰ObservableObject对象。以及订阅ObservableObject里@Published修饰的属性。

1
2
3
4
5
6
7
8
9
10
11
class DataProvider: ObservableObject {
@Published var currentValue = "a value"
}

struct DataOwnerView: View {
@StateObject private var provider = DataProvider()

var body: some View {
Text("provider value: \(provider.currentValue)")
}
}

说明:DataOwnerView持有DataProvider的实例。当DataProvider里的currentValue发生改变的时候,值将在DataOwnerView里实时显示。内部,当DataOwnerView发生刷新需要重新创建的时候,SwiftUI将会管理持有@StateObject包裹的变量。意味着视图中@StateObject修饰的变量只创建1次。SwiftUI将实例和@StateObject关联,当视图再次初始化时将会再次重用@StateObject修饰的对象。换句话说,@StateObject 标记的属性,SwiftUI维护管理着该实例,供视图需要时使用(甚至SwiftUI重新创建渲染视图)。和@State不同的是,@StateObject可用来接收ObservableObject的对象而不是接收一个值类型。

@StateObject使用场景:

  • 你想在ObservableObject里响应或者更新视图。
  • 在视图中,创建了ObservableObject实例,需要用@StateObject修饰。

@ObservedObject

@ObservedObject是用来包裹ObservableObject实例,和@StateObject相似,除了视图不创建拥有@ObservedObject 实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct DataOwnerView: View {
@StateObject private var provider = DataProvider()

var body: some View {
VStack {
Text("provider value: \(provider.currentValue)")

DataUserView(provider: provider)
}
}
}

struct DataUserView: View {
@ObservedObject var provider: DataProvider

var body: some View {
// create body and use / modify `provider`
}
}

DataOwnerView往DataUserView传递了一个@StateObject,而DataProvider实例在DatauserView中被使用。在DatauserView中,当该视图需要刷新渲染的时候,SwiftUI内部不维护不管理 @ObservedObject。SwiftUI知道父视图将会传递ObservedObject过来,用于DatauserView中作为一个属性值来使用即可。

@ObservedObject使用场景:

  • 你需要在ObservedObject响应变更或者更新
  • 在视图中不需创建ObservedObject的实例(如果需要,则用@StateObject)

@EnvironmentObject

有时候你需要在你的App各种各样的地方使用同一个对象,而你又不想一层层传递。你可能想做一个全局依赖可用于让视图的所有节点或者你的App都共享使用到该对象。那么你可以使用@EnvironmentObject。

1
2
3
4
5
6
7
struct EnvironmentUsingView: View {
@EnvironmentObject var dependency: DataProvider

var body: some View {
Text(dependency.currentValue)
}
}

@EnvironmentObject 修饰的对象必须遵循ObservableObject协议,一般可配置在父节点。例如,我们可以在App入口注入一个全局的环境变量,这样我们整个App都可以使用。
1
2
3
4
5
6
7
8
9
10
struct MyApp: App {
@StateObject var dataProvider = DataProvider()

var body: some Scene {
WindowGroup {
EnvironmentUsingView()
.environmentObject(dataProvider)
}
}
}

当@EnvironmentObject 修饰的属性发生改变时候,你的视图将会重新渲染。
@EnvironmentObject使用场景:

  • 正常情况下,你会使用@ObservedObject,但你可能需要把ObservableObject对象传递到多个视图中初始化之前,因为视图渲染的时候可能用到它,那么此刻就用@EnvironmentObject。

总结

通过以上讲解,我们可以把渲染的类型分为值类型和对象。
值类型: 数据怎样使用?常规属性?只读?读或写? 谁持有该数据?来自外界?(@Binding) 还是自身View的状态(@State)?
对象类型: View怎样接收该对象? 作为参数?(@ObservedObject) 自身创建该对象实例?(@StateObject) 通过环境变量?(@EnvironmentObject)

评论