SwiftUI 不能可靠地将更改传播到子视图
SwiftUI does not reliably propagate changes to child view
我在我的 SwiftUI 代码中观察到一个奇怪的行为,并将其缩小到以下最小示例。
给定此示例存储,其中包含书籍模型结构数组。
struct Book: Identifiable {
let id: UUID
var likes: Int
var unusedProperty: String = ""
}
extension Book: Equatable {
static func == (lhs: Book, rhs: Book) -> Bool {
return lhs.id == rhs.id
}
}
class MyStorage: ObservableObject {
@Published var books: [Book] = [
.init(id: .init(uuidString: "B2A44450-BC03-47E6-85BE-E89EA69AF5AD")!, likes: 0),
.init(id: .init(uuidString: "F5AB9D18-DF73-433E-BB48-1C757CB6F8A7")!, likes: 0)
]
func addLike(to book: Book) {
for i in books.indices where books[i].id == book.id {
books[i].likes += 1
}
}
}
并在这个简单的视图层次结构中使用它:
struct ReducedContentView: View {
@StateObject var storage: MyStorage = MyStorage()
var body: some View {
VStack(spacing: 8) {
ForEach(storage.books) { book in
HStack {
VStack(alignment: .leading) {
Text("Top-Level: \(book.likes)")
BookView(book: book)
}
Spacer()
Button("Add Like") {
storage.addLike(to: book)
}
}.padding(.horizontal)
}
}
}
}
struct BookView: View {
let book: Book
var body: some View {
Text("Nested: \(book.likes)")
.foregroundColor(.red)
}
}
对 likes
属性 的任何更改都不会传播到 BookView
,只会传播到“顶级”Text
。
现在,如果我更改以下其中一项,它将起作用:
- 删除
unusedProperty
(生产中需要)
- 将
&& lhs.likes == rhs.likes
添加到 Equatable 一致性(这不是预期的)
- 修改
BookView
以接受 @Binding var book: Book
而不是 let
最后一个选项是我可以在我的生产代码中采用的东西 - 尽管如此,我真的很想了解这里发生了什么,所以任何提示都将不胜感激。
这是您自定义 Equatable
一致性的结果:
extension Book: Equatable {
static func == (lhs: Book, rhs: Book) -> Bool {
return lhs.id == rhs.id
}
}
如果删除它,它将按预期工作。
在您当前的代码中,您是说如果两个 Book
具有相同的 ID,则它们 相等 。我怀疑你实际上并不是说它们真正平等——你只是说它们是同一本书。
你的第二个选项(“将 && lhs.likes == rhs.likes
添加到 Equatable
一致性(这不是预期的)”)本质上只是使用系统生成的综合 Equatable
一致性,因为 unusedProperty
未使用 - 因此,如果您要使用第二个选项,您也可以完全删除自定义 ==
函数。
我认为这里要做的决定是你是否真的想告诉系统一个谎言(两个 Book
,无论它们的其他属性如何,都 相等 如果它们共享相同的 id
) 或者您是否应该让系统自行判断项目是否相等。
我在我的 SwiftUI 代码中观察到一个奇怪的行为,并将其缩小到以下最小示例。
给定此示例存储,其中包含书籍模型结构数组。
struct Book: Identifiable {
let id: UUID
var likes: Int
var unusedProperty: String = ""
}
extension Book: Equatable {
static func == (lhs: Book, rhs: Book) -> Bool {
return lhs.id == rhs.id
}
}
class MyStorage: ObservableObject {
@Published var books: [Book] = [
.init(id: .init(uuidString: "B2A44450-BC03-47E6-85BE-E89EA69AF5AD")!, likes: 0),
.init(id: .init(uuidString: "F5AB9D18-DF73-433E-BB48-1C757CB6F8A7")!, likes: 0)
]
func addLike(to book: Book) {
for i in books.indices where books[i].id == book.id {
books[i].likes += 1
}
}
}
并在这个简单的视图层次结构中使用它:
struct ReducedContentView: View {
@StateObject var storage: MyStorage = MyStorage()
var body: some View {
VStack(spacing: 8) {
ForEach(storage.books) { book in
HStack {
VStack(alignment: .leading) {
Text("Top-Level: \(book.likes)")
BookView(book: book)
}
Spacer()
Button("Add Like") {
storage.addLike(to: book)
}
}.padding(.horizontal)
}
}
}
}
struct BookView: View {
let book: Book
var body: some View {
Text("Nested: \(book.likes)")
.foregroundColor(.red)
}
}
对 likes
属性 的任何更改都不会传播到 BookView
,只会传播到“顶级”Text
。
现在,如果我更改以下其中一项,它将起作用:
- 删除
unusedProperty
(生产中需要) - 将
&& lhs.likes == rhs.likes
添加到 Equatable 一致性(这不是预期的) - 修改
BookView
以接受@Binding var book: Book
而不是let
最后一个选项是我可以在我的生产代码中采用的东西 - 尽管如此,我真的很想了解这里发生了什么,所以任何提示都将不胜感激。
这是您自定义 Equatable
一致性的结果:
extension Book: Equatable {
static func == (lhs: Book, rhs: Book) -> Bool {
return lhs.id == rhs.id
}
}
如果删除它,它将按预期工作。
在您当前的代码中,您是说如果两个 Book
具有相同的 ID,则它们 相等 。我怀疑你实际上并不是说它们真正平等——你只是说它们是同一本书。
你的第二个选项(“将 && lhs.likes == rhs.likes
添加到 Equatable
一致性(这不是预期的)”)本质上只是使用系统生成的综合 Equatable
一致性,因为 unusedProperty
未使用 - 因此,如果您要使用第二个选项,您也可以完全删除自定义 ==
函数。
我认为这里要做的决定是你是否真的想告诉系统一个谎言(两个 Book
,无论它们的其他属性如何,都 相等 如果它们共享相同的 id
) 或者您是否应该让系统自行判断项目是否相等。