SwiftUI: почему SwiftUI перестраивает представление при изменении нерелевантного свойства данных @Observable

Я новичок в SwiftUI. Извините, если это глупый вопрос.

@Observable
class Book {
    var title = "A Sample Book"
    var pageOne = Page()
}

class Page {
    var pageTitle = "pageTitle"
    var isAvailable = true
}

struct ContentView: View {
    @State private var book = Book()
    
    var body: some View {
        VStack{
            ChangeView(book: book)
            DisplayView(book: book)
        }
    }
}

struct ChangeView: View{
    @Bindable var book: Book
    
    var body: some View{
        Toggle(book.pageOne.isAvailable ? "page available" : "page not available",
               isOn: $book.pageOne.isAvailable
        )
    }
}

struct DisplayView: View {
    var book: Book

    var body: some View {
        Text(book.pageOne.pageTitle)
    }
}

ChangeView читает и смотрит book.pageOne.isAvailable. Каждый раз, когда я нажимаю переключатель, тело ChangeView перестраивается. Это имеет смысл. Но я обнаружил, что тело DisplayView также перестраивается каждый раз, когда я нажимаю переключатель, даже если оно вообще не заботится о isAvailable. Итак, мой вопрос: зачем SwiftUI перестраивать DisplayView, если переменная не имеет значения? Кажется, это отличается от того, что сказано в документе. Это потому, что isAvaialble является подсвойством данных Observable?

Что произойдет, если вы поставите @Observable class Page или struct Page?

workingdog support Ukraine 02.09.2024 01:12

Обнаружение изменения свойства не является «вложенным». Если что-то меняется в Page, то изменение запускается в свойстве pageOne вашего Book. Вы можете создать Page и @Observable, а затем передать book.pageOne в DisplayView вместо book.

Paulw11 02.09.2024 02:50
@State предназначен только для значений или структур, для классов — @StateObject
malhal 02.09.2024 14:57

@workingdogsupportUkraine Я только что проверил. Явление то же самое.

LittleExp 03.09.2024 16:02

@Paulw11 Спасибо, я так и предполагал. Но мне просто нужен кто-то, кто подтвердит мои мысли.

LittleExp 03.09.2024 16:24
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
5
52
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Во-первых, вам также следует сделать Page@Observable. В противном случае SwiftUI не сможет должным образом отслеживать свои изменения.

@Observable
class Page {
    var pageTitle = "pageTitle"
    var isAvailable = true
}

Однако одного этого недостаточно, чтобы решить проблему. Чтобы это исправить, вы можете либо поставить Bindable вместо Page (в этом случае book не обязательно должно быть @Bindable),

@Bindable var page = book.pageOne
Toggle(book.pageOne.isAvailable ? "page available" : "page not available",
       isOn: $page.isAvailable
)

или сделайте:

Toggle(book.pageOne.isAvailable ? "page available" : "page not available",
       isOn: $book[dynamicMember: \.pageOne.isAvailable]
)

Объяснение:

Когда вы это делаете $book.pageOne.isAvailable, он снижается (т. е. эквивалентно) до:

$book[dynamicMember: \.pageOne][dynamicMember: \.isAvailable]

Первая пара [] — это вызов Bindable.subscript . Это создает Binding<Page>. Вторая пара [] — это вызов Binding.subscript, создающий Bindable<Bool>.

Если вы понятия не имеете, о чем идет речь, посмотрите SE-0195 и SE-0252.

Проблема в этом промежуточном Binding<Page>. Когда последний Binding<Bool> изменяется переключателем, очевидно, вызывается установщик isAvailable, но, по моим наблюдениям, также вызывается установщик pageOne.

Поскольку Page является ссылочным типом, этот вызов сеттера не нужен. Возможно, это потому, что Binding<Bool> производится из Binding<Page>. В конце концов, Binding изначально предназначены для работы со структурами, и если бы Page была структурой, абсолютно необходимо вызвать установщик pageOne.

В любом случае, поскольку вызывается установщик pageOne, когда isAvailable изменяется, SwiftUI думает, что pageOne тоже меняется. Поскольку DisplayView.body вызывает метод получения pageOne, SwiftUI будет вызывать DisplayView.body снова, когда isAvailable изменится.

В обоих решениях я исключил промежуточный Binding<Page>, поэтому установщик pageOne не вызывается.

Большое спасибо за ваш ответ. Это объясняет все. Причина, по которой я привел этот пример, заключается в том, что когда я его изучал, мне было любопытно, как этот механизм реализован внутри и какого уровня функциональности он может достичь. Для нас, новичков, иногда документация недостаточно подробная, и мы не можем видеть внутреннюю реализацию, что может сильно расстраивать, когда мы сталкиваемся с некоторыми странными явлениями.

LittleExp 03.09.2024 16:22

Другие вопросы по теме