SwiftUI中的`@State`关键字有什么作用?

64

SwiftUI教程 使用@State关键字表示可变的UI状态:

@State var showFavoritesOnly = false

它提供了以下摘要:

State是一个值或一组值,可以随时间而改变,并影响视图的行为、内容或布局。使用具有@State属性的属性向视图添加状态。

  • 这个关键字到底是什么意思?
  • 如何通过修改@State变量来触发视图重新计算?
  • body getter中的其他变量如何是不可变的?

@JoakimDanielson 这是一个不好的问题吗? - Taylor
好的,我只是真的很好奇这是如何实现的,需要添加哪些语言特性。 - Taylor
15
@JoakimDanielson,你不能指望没有人对新技术提出问题。 - J. Doe
@JoakimDanielson,似乎已经有足够的信息可以给出一个相当不错的答案了。 - Taylor
7个回答

67

@State关键字是一个@propertyWrapper,这是Swift 5.1中最近引入的一项功能。正如相应的提案所解释的那样,它是一种值包装器,避免了样板代码。


附注:@propertyWrapper以前被称为@propertyDelegate,但自那时以来已经改变了。有关更多信息,请参见此帖子


官方@State文档有以下内容:

  

SwiftUI管理您声明为状态的任何属性的存储。   当状态值更改时,视图将失效并重新计算主体。将状态用作给定视图的单一真相来源。

     

状态实例不是值本身;它是读取和修改值的一种手段。要访问状态的基础值,请使用其值属性。

因此,当你初始化一个标记为@State的属性时,你实际上并没有创建自己的变量,而是促使SwiftUI在后台创建一个"something"来存储你设置的内容,并从现在开始监控它!你的@State var只是作为访问这个包装器的委托

每次你写入@State变量时,SwiftUI会知道,因为它正在监视它。它还会知道是否从Viewbody读取了@State变量。利用这些信息,它将能够重新计算引用了body@State变量的任何View,在这个变量发生变化后。


2
它们现在还被称为属性委托或是属性包装器吗?也许两者都有 :( - 23inhouse
这是很好的信息,但它是如何实际工作的:“当状态值改变时,视图会使其外观无效并重新计算主体”?在整合视频中,那个意大利人说SwiftUI知道状态属性在主体中使用,并且还知道它在哪个视图中。我不明白这是如何可能的,你呢? - Gusutafu
2
@23inhouse 我已经在我的帖子中添加了关于这个的澄清。简而言之:它们的意思相同,但是 @propertyWrapper 更受欢迎。 - fredpi
1
作为一个 @propertyWrapper,它是一个包装器,不仅知道何时被写入,也知道何时被读取 - 这样,它可以确定从哪些视图中读取。我在我的回答中添加了关于此的说明。 - fredpi

8

如果您熟悉React Native,我还想补充一些内容。

@State属性非常类似于React Native中的this.state对象。

例如:

struct Foobar: some View {
    @State var username = ""
}

class Foobar extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      username: '',
    };
  }
}

当您修改用户名变量时,它们将产生相同的效果,即重新呈现当前页面。

SwiftUI 有 props 吗? - Lee Irvine
[@Lee Irvine] 是的,但并不完全相同,构造函数参数等同于 props 对象。因为它们都来自于它们的父控件/组件。 - s1ro6

6

WWDC视频-204会议中有一个很好的例子来解释它(从16:00开始,引文起始于20:15)。

@State变量的特殊属性之一是SwiftUI可以观察它们何时被读取和写入。因为SwiftUI知道zoomedbody中被读取,所以它知道视图的渲染取决于它。这意味着当一个变量发生改变时,框架将使用新的@State值再次请求body

SwiftUI中的数据流程(5:38)也详细阐述和证明了作为属性包装器的@State。它展示了如何解决我们在一个不可变的(structView中需要一个可变值的问题。


3
什么机制使Swift能够实现这一点?“因为SwiftUI知道zoomed在body中被读取”。 - Gusutafu
2
@Gusutafu Combine框架(发布和订阅)。SwiftUI提供了订阅者,因此它会在每次发布者发送变量已更改的消息时进行监听。作为响应,它调用body以获取全新生成的界面。 - matt
1
谢谢,但我遇到的问题更多是 SwiftUI 或 State 本身如何知道 State 在特定的 body 中被使用! - Gusutafu

3

如果您点击进入@State,则可以看到它有几个getter。其中一个是使用Value,另一个是使用Binding<Value>。 SwiftUI似乎在很大程度上依赖响应式编程(以及他们的新Combine框架),由于我们无法看到这些封装的完整实现,因此我希望通过@State属性封装存储的值是由Combine中的CurrentValueSubject来管理的。顾名思义,这实质上是存储当前值,然后可以通过使用$语法将其用作可绑定属性。


3

我喜欢斯坦福大学的Paul Hegarty教授的讲解方式。他说@State基本上将该变量转换为指向内存中其他位置的某个布尔指针,这就是值改变的地方。但是您的变量-现在是具有@State的指针-不会改变,它始终指向内存中的那个位置。


1

注:接受的答案是正确的,但如果您正在尝试寻找更简单的解释,我在下面做了尝试


@State 属性包装器允许我们更改结构体内部的值。

注意:由于结构体是值类型,因此无法更改其内部属性,因此不允许更改。

struct ExampleTextView: View {

    // Declare a variable with state property wrapper
    @State private var shouldChangeText: Bool = false
    
    var body: some View {
        Text("Just an example")
            .foregroundColor(Color.white)
    }
}
  • 如果你想知道是否应该添加私有访问修饰符?

    是的,作为经验法则,苹果建议 @State 属性不应与其他视图共享,因为还有更具体的属性包装器 @ObservedObject 和 @EnvironmentObject。

  • 但如果它是值类型,如何进行变异,它存储在哪里?

    所以,每当我们使用 @State 标记一个属性时,我们将其存储从结构体中移出,并移至由 SwiftUI 管理的共享存储中。

如果您正在与 Swift 进行比较,那么这就是它的做法:

struct Example {
    var exampleType: String
    
    mutating func changeType() {
        exampleType = "Are we allowed to do this?"
    }
}

致谢:请注意,本答案的灵感来自于此文章https://www.hackingwithswift.com/quick-start/swiftui/what-is-the-state-property-wrapper


0
如果您了解C#和Windows开发,那么@Statex:BindBinding非常相似,甚至可以说是一样的。在集合上,它与ObservableCollection非常相似,甚至可以说是一样的。
正如fredpi所说,SwiftUI使用@State属性委托来监听变量的更新。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接