onAppear срабатывает дважды — или не срабатывает, когда ждёшь. Чем это отличается от жизненного цикла UIKit?
Этот экран загружает ленту в onAppear, но сетевой запрос выполняется больше одного раза — после ухода с экрана и возврата в списке оказываются дублированные элементы.
Требования:
- Объясните, почему
onAppearможет выполниться больше одного раза для того же экрана. - Сделайте так, чтобы загрузка происходила однократно и отменялась при уходе представления.
struct FeedView: View {
@State private var items: [Item] = []
var body: some View {
List(items) { RowView(item: $0) }
.onAppear { Task { items = try await api.loadFeed() } } // снова при переустановке
}
}
Найдите и исправьте ошибку.
onAppear следует за структурной идентичностью представления, а не за циклом контроллера, и срабатывает при каждом входе в иерархию. Переустановленное представление вызывает его снова; так и не вставленное — не вызывает. Это не viewDidAppear «раз на экран».
- ✗Считают
onAppearэквивалентомviewDidAppear«раз на экран» - ✗Кладут чувствительную к повторам настройку в
onAppearбез защиты - ✗Ждут, что
onAppearсработает для представления, которое так и не вставлено
- →Почему переопределённое по идентичности представление снова вызывает
onAppear? - →Куда поместить логику загрузки, чтобы повторная вставка её не повторяла?
onAppear is not viewDidAppear. It is tied to the view's structural identity and fires every time that view is inserted into the render tree — navigating away and back re-inserts FeedView, so onAppear runs again and appends a second copy of the feed. It can also never run for a view that is never inserted (behind a false condition or below the fold of a lazy container).
Prefer .task, which starts when the view appears and is automatically cancelled when it disappears — and give it an id so it does not repeat unless the identity you key on changes:
struct FeedView: View {
@State private var items: [Item] = []
var body: some View {
List(items) { RowView(item: $0) }
.task { // auto-cancelled on disappear
guard items.isEmpty else { return }
items = (try? await api.loadFeed()) ?? []
}
}
}
If you must stay on onAppear, guard the work with a stored flag or move ownership of the loaded data into a model that survives re-insertion. The rule: never assume onAppear runs exactly once.