SwiftUISwiftUI

SwiftUI 探索 - 状态和数据流

2019-06-12  本文已影响11人  hexiaoxiao

SwiftUIiOS13新出的声明式UI框架,将会完全改变以前命令式操作UI的开发方式。此文章主要介绍SwiftUI中状态管理的方式。

可变状态

@State

ReactFlutter中的State类似,只不过ReactFlutter中需要显式调用setState方法。在SwiftUI 中直接修改State属性值,就触发视图更新。

因为State是使用了@propertyDelegate修饰的属性值,其内部实现应该是在状态值set方法中进行变更视图的操作。

class Model: BindableObject {
    var didChange = PassthroughSubject<Model, Never>()
    var count: Int = 0 {
        didSet {
            didChange.send(self)// 调用didChange触发变更操作
        }
    }
}
struct ContentView: View {
   @State private var text: String = "a"// 使用@State修饰
   @State private var model = Model()// 使用@State修饰
    var body: some View {
        VStack {
          Text(text)
          Text(model.count)
          Button(action: {
              self.text = "b"// 修改text会更新视图
              self.count += 1
            }) {
              Text("update-text")
          }
        }
    }
}

State属性修改时,会检测State属性被使用和检测值变更来决定要不要更新视图和触发body方法。

属性

Property

React中的Props类似,用于父视图向子视图传递值。

struct PropertyView: View {
    let text: String// 当text改变时,会重新计算`body`。
    var body: some View {
        Text(text)
    }
}
struct ContentView: View {
    var body: some View {
        PropertyView(text: "a")
    }
}

@Binding

Property功能类似,用于父视图向子视图传递值。只不过Binding属性可以修改,修改Binding属性会触发父视图State改变重新计算body。可以实现反向数据流的功能,有点类似MVVM的双向绑定。

struct BindingView : View {
    @Binding var text: String // 使用@Binding修饰
    var body: some View {
        VStack {
            Button(action: {
                self.text = "b"
            }) {
                Text("update-text")
            }
        }
    }
}
struct ContentView : View {
    @State private var text: String = "a" // State
    var body: some View {
        VStack {
            BindingView(text: $text)// State变量使用$获取Binding
            Text(text)
        }
    }
}

@ObjectBinding

@ObjectBinding似乎和State相似,暂时不太清楚使用上有什么区别。@State替换@ObjectBinding使用没有问题,@Binding替换@ObjectBinding使用也没有问题。

class Model: BindableObject {
    var didChange = PassthroughSubject<Model, Never>()
    var count: Int = 0 {
        didSet {
            didChange.send(self)
        }
    }
}
struct ChildView: View {
//    @Binding var model: Model
//    @ObjectBinding var model: Model
    var body: some View {
        VStack {
            Text("count2-\(model.count)")
            Button(action: {
                self.model.count += 1
            }) {
                Text("update")
            }
        }
    }
}

struct ContentView : View {
//    @State private var model = Model()
//    @ObjectBinding private var model = Model()
    var body: some View {
        VStack {
            ChildView(model: model)
            Text("count1-\(model.count)")
        }
    }
}

上面StateObjectBindingBinding注释的地方任意使用结果都一样,视图能正确更新。

@EnvironmentObject

通过Property或者Binding的方式,我们只能显式的通过组件树逐层传递。

显式逐层传递的缺点

  • 当组件树复杂的时候特别繁琐,修改起来也很麻烦。
  • 有些属性在视图树中间的层级不会使用到,只有底层会使用。会增加中间层级视图的复杂度。也可以避免中间的层级重复计算body触发视图更新。

为了避免层层传递属性,可以使用Environment变量。Environment属性可以在任意子视图获取并使用。和React中的Context很相似。

struct EnvironmentView1: View {
    var body: some View {
        return VStack {
            EnvironmentView2()
            EnvironmentView3()
        }
    }
}
struct EnvironmentView2: View {
    @EnvironmentObject var model: Model// 使用@EnvironmentObject修饰属性
    var body: some View {
       Button(action: {
                self.model.change()
            }) {
                Text("update-Environment")
            }
    }
}
struct EnvironmentView3: View {
    @EnvironmentObject var model: Model// EnvironmentObject
    var body: some View {
        Text(model.text)
    }
}
struct ContentView: View {
    var body: some View {
        //EnvironmentObject需要使用environmentObject方法注入到组件树中
        EnvironmentView1().environmentObject(Model())
    }
}

目前好像没有方式实现根据不同的key来注入多个对象并获取。

数据流

父视图 -> 子视图向下传递

父视图 -> 子视图跨层级向下传递

全局状态层管理

视图更新流程

Tips

关于 State

class Model: BindableObject {
    var didChange = PassthroughSubject<Model, Never>()
    var count: Int = 0 {
        didSet {
            didChange.send(self)
        }
    }
    init() {
        print("Model-init-\(count)")// 这里count始终为0
    }
}
struct Struct {
    private(set) var count = 0
    init() {
        print("Struct-\(count)")// 这里count始终为0
    }
    mutating func update() {
        print("update-\(count)")
        count += 1
    }
}
struct ChildView: View {
    @State private var model2 = Struct()
    @State private var model = Model2()
    @State private var count = 0
    var body: some View {
        return VStack {
            Text("\(model.count)")
            Text("\(model2.count)")
            Text("\(count)")
            Button(action: {// 修改 State
                self.model.count += 1
                self.count += 1
                self.model2.update()
            }) {
                Text("update")
            }
            }
        }
}
struct ContentView: View {
    @State private var count = 0
    var body: some View {
        return VStack {
            ChildView()
            Button(action: {
                self.count += 1
            }) {
                Text("update")
            }
            Text("\(count)")
        }
    }
}

不太清楚这里是如何处理的,State虽然重新初始化了一次,似乎还是使用的之前的State

  1. 例如当点击Button时,会修改ChildViewmodel, model2count+=1,当前count=1。
  2. ChildView重新创建时,modelmodel2初始化方法中,count=0。
  3. 当下一次点击Button修改count值时,count会在1的基础上+1,之后count=2。

性能

上一篇 下一篇

猜你喜欢

热点阅读