Skip to main content

Обробка помилок у Vue.js

Обробка помилок у Vue.js перехоплює виключення в компонентах і не дає їм обвалити весь застосунок.

Теорія

TL;DR

  • onErrorCaptured у батьківському компоненті перехоплює синхронні помилки будь-якого нащадка під час render, setup або watch
  • Як протипожежна смуга: помилка доходить до межі і далі не поширюється
  • Повернення false з хука зупиняє спливання вгору по дереву компонентів
  • Лише синхронні throws. Async-функції та Promise rejection потребують try/catch
  • app.config.errorHandler - глобальний запасний обробник для всього, що пропустили локальні межі

Швидкий приклад

vue
<!-- ErrorBoundary.vue --> <script setup> import { ref, onErrorCaptured } from 'vue' const hasError = ref(false) const errorMsg = ref('') onErrorCaptured((err, instance, info) => { hasError.value = true errorMsg.value = err.message // наприклад "Failed to parse JSON" // info вказує де: "render", "setup()", "watcher" return false // зупиняємо спливання }) </script> <template> <div v-if="hasError" class="error-state"> {{ errorMsg }} <button @click="hasError = false">Повторити</button> </div> <slot v-else /> <!-- дочірній компонент падає тут, помилка перехоплюється --> </template>

Обгорни будь-який нестабільний дочірній компонент цією межею. Коли він кидає помилку, слот перемикається на стан помилки. Решта сторінки залишається.

Як поширюються помилки

Vue обробляє помилки компонентів подібно до DOM-подій: вони спливають вгору по дереву. Синхронна помилка в setup() або render онука піднімається до найближчого предка з onErrorCaptured. Хук отримує три аргументи: об'єкт помилки, екземпляр компонента і рядок info на кшталт "render" або "setup()".

Якщо хук повертає false, спливання зупиняється. Якщо ні, помилка продовжує підніматися і зрештою доходить до app.config.errorHandler. Цей глобальний обробник спрацьовує останнім, для всього що локальні межі не зупинили.

Параметр info варто логувати. "setup()" означає, що компонент впав під час ініціалізації. "render" - під час обчислення шаблону. Без цього контексту дебагінг займає більше часу.

Коли що використовувати

  • Ізолювати один нестабільний віджет від решти UI: onErrorCaptured у компоненті-обгортці навколо нього
  • Логувати все в Sentry або Datadog: app.config.errorHandler у main.ts
  • Fetch повертає помилку всередині компонента: try/catch навколо await
  • Необроблені Promise rejection поза Vue: window.addEventListener('unhandledrejection', ...)
  • Попередження тільки на dev: app.config.warnHandler, прибрати з production білда

onErrorCaptured не підходить для async-коду. Це перша помилка, на яку натрапляє більшість розробників.

Як це працює всередині

Коли синхронна помилка виникає в setup або render-циклі дочірнього компонента, Vue перехоплює її ще до того, як вона може зіпсувати стан батька. Невдале піддерево пропускається, і керування передається системі обробки помилок, яка піднімається через onErrorCaptured-хуки. DOM батька залишається примонтованим. У Vue 3.4+, помилки async setup всередині <Suspense> передаються найближчій межі, а не обходять її.

Типові помилки

1. Очікування, що onErrorCaptured перехоплює async rejection.

vue
<script setup> async function loadData() { throw new Error('fetch failed') } loadData() // Promise відхиляється, onErrorCaptured не спрацює </script>

Рішення: обгортай async-виклики в try/catch і вручну оновлюй реактивний ref з помилкою.

2. Забутий return false.

vue
onErrorCaptured((err) => { console.log(err) // логує, але помилка все одно спливає далі })

Без return false помилка доходить до app.config.errorHandler і консолі браузера. Додавай, якщо хочеш утримати помилку саме в цій межі.

3. Межа в тому самому компоненті, що кидає помилку.

onErrorCaptured перехоплює лише помилки нащадків, не власні. Якщо компонент кидає у своєму setup(), хук у цьому ж компоненті не спрацює. Використовуй окрему обгортку ErrorBoundary.vue.

4. Немає механізму повтору.

Просто очистити error ref недостатньо, якщо в дочірньому компоненті залишився поганий стан. При повторі скидай дані, що спричинили падіння, а потім перемонтовуй дочірній компонент. Кнопка "Спробувати ще", що лише очищає ref, часто одразу перезапускає ту саму помилку.

5. Ігнорування параметра info.

Рядки "render", "setup()", "watcher" точно вказують на фазу lifecycle. Логування тільки err.message губить цей контекст. Передавай info у Sentry як додаткові дані.

Де зустрічається в реальних проектах

  • Nuxt.js: встановлює app.config.errorHandler у plugins/error-handler.client.ts для виявлення SSR mismatch. Потрібен guard process.client.
  • Quasar Framework: обгортає layout-компоненти межами з auto-retry.
  • VueUse: composable-и на кшталт useFetch повертають реактивний error ref, тому незловлені виключення не потрапляють до коду компонента.
  • Sentry: передавай err до Sentry.captureException(err) всередині app.config.errorHandler, додавай info і ім'я компонента як extra.

Питання на співбесіді

Q: Які помилки перехоплює onErrorCaptured?
A: Синхронні помилки в setup(), render, watchers і lifecycle hooks нащадків. Не перехоплює помилки з emit, async-функцій без await у відстежуваному контексті, і помилки в setTimeout.

Q: У чому різниця між onErrorCaptured і app.config.errorHandler?
A: onErrorCaptured локальний і спрацьовує першим. app.config.errorHandler глобальний і спрацьовує останнім. Використовуй обидва: межі для ізоляції UI, глобальний обробник для логування.

Q: Як onErrorCaptured взаємодіє з <Suspense>?
A: У Vue 3.4+, помилки async setup у <Suspense> передаються найближчій межі. На старших версіях вони могли її оминати. Для надійності обгортай async setup у try/catch.

Q: Чому межа помилок може не спрацювати в Nuxt SSR?
A: Хук запускається на клієнті. Під час server rendering він може не спрацювати. Використовуй guard process.client у Nuxt-плагінах і обробляй серверні помилки в server middleware.

Q: (Senior) Як побудувати межу помилок з відновленням стану Pinia при повторі?
A: У handler повтору виклич store.$reset() до очищення error ref. Потім використай nextTick, щоб дочірній компонент перемонтувався з чистим станом. Без nextTick компонент може перерендеритись до завершення скидання стору.

Приклади

Базова межа помилок

vue
<!-- ErrorBoundary.vue --> <script setup> import { ref, onErrorCaptured } from 'vue' const error = ref(null) onErrorCaptured((err, instance, info) => { error.value = { message: err.message, info } return false }) </script> <template> <div v-if="error" class="fallback"> <p>{{ error.message }} (у {{ error.info }})</p> <button @click="error = null">Повторити</button> </div> <slot v-else /> </template>
vue
<!-- App.vue --> <template> <ErrorBoundary> <UserDashboard /> </ErrorBoundary> </template>

Коли UserDashboard кидає помилку під час render або setup, межа перехоплює її і показує fallback. Клік на "Повторити" очищає ref і перерендеровує слот.

Віджет дашборду з проблемними API-даними

vue
<!-- DashboardWidget.vue --> <script setup> import { ref, onErrorCaptured } from 'vue' import UserChart from './UserChart.vue' const error = ref(null) onErrorCaptured((err) => { error.value = err.message // наприклад "Failed to parse JSON" return false }) function retry() { error.value = null } </script> <template> <div class="dashboard-card"> <h3>Метрики користувачів</h3> <div v-if="error" class="error-fallback"> {{ error }} <button @click="retry">Оновити дані</button> </div> <UserChart v-else /> </div> </template>

UserChart парсить дані API під час setup. Якщо API повертає некоректний JSON, парсинг кидає синхронну помилку, межа перехоплює її і показує кнопку оновлення. Решта дашборду продовжує нормально відображатися.

Глобальний обробник з Sentry

typescript
// main.ts import { createApp } from 'vue' import * as Sentry from '@sentry/vue' import App from './App.vue' const app = createApp(App) app.config.errorHandler = (err, instance, info) => { Sentry.captureException(err, { extra: { info, component: instance?.$options?.name ?? 'unknown', }, }) console.error('[global error]', err) } app.mount('#app')

Пеrehоплює все, що не зупинили локальні межі. Рядок info і ім'я компонента допомагають знайти проблемну частину застосунку без копання в стек трейсах.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.

Дочитали статтю?