SwiftUI中@Observable视图模型搭配@State的使用时机及视图重建问题咨询
嘿,这个问题我之前也踩过一模一样的坑!明明代码结构看起来差不多,一个不用@State就能正常跑,另一个缺了@State就各种UI抽风,那种困惑感太真实了。咱们一点点拆解清楚:
首先得先搞懂核心逻辑:@Observable的视图模型如果是普通属性,它的生命周期是和视图实例绑定的——一旦SwiftUI把你的视图实例销毁重建,这个VM就会被重新初始化,之前的所有状态直接清零。而@State的作用,就是把VM的存储从视图实例里“挪”到SwiftUI自己管理的视图树状态容器里,不管视图怎么重建,VM的实例都会被稳稳保留住,状态也不会丢。
先聊聊你的两个例子为啥表现不一样
第一个AddProductView不用@State也正常,大概率是因为这个视图从来没被SwiftUI重建过——比如它可能是根视图,或者父视图的状态变化没触发它的销毁重建,VM的实例从始至终都是同一个,@Bindable能正常监听它的属性变化,UI自然能跟着更新。
而ProductDetailsView就不一样了:你在.onAppear里调用fetchProduct(),如果不用@State,视图很可能在.onAppear执行前后被重建了一次——旧的视图实例被销毁,新的视图实例带着全新初始化的VM出现,之前设置的showErrorAlert状态直接没了,UI当然就不更新,甚至看起来像是“重置”了。
哪些情况会触发SwiftUI重建你的视图?
这些场景是最常见的“坑点”:
- 父视图的状态变化:比如父视图的@State、@Binding属性更新了,导致父视图重新计算body,这时候如果你的子视图是直接在父视图body里创建的(比如写
ProductDetailsView()),SwiftUI很可能会销毁旧的子视图实例,创建新的,你的普通VM属性就跟着重置了。 - 视图标识变化:如果你给视图加了
.id()修饰符,当id的值变化时,SwiftUI会把这个视图当成完全全新的对象,直接销毁旧实例重建,VM自然也没了。 - 导航栈操作:比如从导航栈返回再重新进入某个视图,SwiftUI有时候会把之前的视图实例彻底销毁,重新创建一个,这时候普通VM的状态就全丢了。
- 设备布局变化:比如旋转设备、切换分屏模式,或者父视图的布局容器(比如从HStack改成VStack)发生变化,都可能触发视图树的调整,导致你的视图被重建。
- 某些生命周期回调的触发:比如
.onAppear、.task,如果视图在这些回调执行前被重建,新的VM实例会覆盖旧的,你之前的操作就白做了。
什么时候必须用@State包裹@Observable的VM?
一句话总结:当你需要VM的状态在视图的多次重建之间被保留时,就必须用@State。具体到业务场景里:
- 视图需要跨生命周期保留状态:比如你的
ProductDetailsView里的错误提示,需要在视图出现后一直存在,不能因为视图重建就消失,这时候@State是必须的。 - 在生命周期回调里修改VM状态:只要你在
.onAppear、.task、.onChange这些回调里修改VM的属性,几乎都得用@State——不然视图一重建,回调里的操作就等于在新的VM上执行,旧状态直接丢了。 - 视图是动态生成的:比如在List、ForEach里的子视图,当列表数据源变化时,SwiftUI可能会重建部分子视图,这时候必须用@State来保留每个子视图的VM状态。
- 视图可能被导航/布局操作反复创建销毁:比如需要频繁进入退出的详情页、编辑页,这类视图的VM一定要用@State,不然每次进入都得重新初始化,状态全丢。
最后再补个小提醒
别被第一个例子的“正常工作”误导——那只是刚好那个视图没被重建而已。实际开发里,几乎所有承载业务逻辑的视图,都有可能因为各种原因被SwiftUI重建,所以我的个人习惯是:只要是@Observable的视图模型,一律用@State存储,省得哪天突然遇到UI抽风的情况,再回头排查就麻烦了。
内容来源于stack exchange

