Suggest an editImprove this articleRefine the answer for “Vue components and their lifecycles”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Vue component lifecycle** defines the phases a component passes through from creation to removal, with hooks to run code at each point. ```js // Options API // Composition API beforeCreate() {} // top of setup() created() {} // top of setup() mounted() {} onMounted(() => {}) beforeUpdate() {} onBeforeUpdate(() => {}) updated() {} onUpdated(() => {}) beforeUnmount() {} onBeforeUnmount(() => {}) ``` **Key rule:** DOM is only available from `mounted`/`onMounted` onward. Use `beforeUnmount`/`onBeforeUnmount` to cancel timers and fetch requests.Shown above the full answer for quick recall.Answer (EN)Image**Vue component lifecycle** defines the ordered phases every Vue component passes through from creation to removal, with hooks to run code at each point. ## Theory ### TL;DR - Think of it like a restaurant server's shift: hired (`beforeCreate`/`created`), starts work (`beforeMount`/`mounted`), handles orders (`beforeUpdate`/`updated`), clocks out (`beforeUnmount`/`unmounted`) - Options API uses `mounted`; Composition API uses `onMounted` - same timing, different syntax - DOM does not exist until `mounted`/`onMounted` - accessing `$refs` in `created` returns undefined - Use hooks for side effects (API calls, timers, DOM setup); use `watch`/`watchEffect` for reactive logic - SSR skips `beforeMount`/`mounted` entirely - put shared server/client logic in `created`/`setup` ### Quick Example ```js // Options API: lifecycle order on mount then destroy export default { name: 'LifecycleDemo', data() { return { count: 0 }; }, beforeCreate() { console.log('1. beforeCreate - data not ready:', this.count); }, // undefined created() { console.log('2. created - data ready:', this.count); }, // 0 beforeMount() { console.log('3. beforeMount - no DOM yet'); }, mounted() { console.log('4. mounted - DOM accessible'); }, beforeUnmount(){ console.log('5. beforeUnmount - cleanup here'); }, unmounted() { console.log('6. unmounted'); } }; // Console: 1 → 2 → 3 → 4 ... then on destroy: 5 → 6 ``` `data()` initializes between `beforeCreate` and `created`. The DOM only exists from `mounted` onward. ### Options API vs Composition API Options API hooks (`beforeCreate`, `mounted`) sit directly on the component object, like methods on a class. Straightforward, but related logic ends up scattered across separate option blocks in the same file. Composition API wraps the same timing inside `setup()` using functions like `onMounted` and `onBeforeUnmount`. There is no direct `beforeCreate` equivalent because `setup()` itself runs at that moment. The payoff: tree-shakable, reusable composables without `this`. ```js // Composition API - same timing, different structure import { ref, onMounted, onBeforeUnmount } from 'vue'; export default { setup() { const count = ref(0); onMounted(() => console.log('mounted, count:', count.value)); // 0 onBeforeUnmount(() => console.log('cleaning up')); return { count }; } }; ``` Both APIs produce the same hook timing. The difference is code organization, not behavior. ### When to Use Each Hook - **Initial data fetch**: `created` (Options) or `onMounted` (Composition). `created` starts the request before DOM renders; `onMounted` is safer for SSR and covers most cases - **DOM access**: `mounted`/`onMounted` only. The DOM simply does not exist before this point - **Cleanup on leave**: `beforeUnmount`/`onBeforeUnmount`. Cancel fetch controllers, clear intervals, remove event listeners here - **Reacting to changes**: `updated`/`onUpdated` or better, `watchEffect`. Avoid heavy logic in `updated` since it fires on every reactive change - **SSR-safe logic**: `created`/`setup` run on both server and client; `mounted` runs only in the browser ### How It Works Internally During `created`/`setup`, Vue scans the template and wraps `data`/`ref` properties in Proxy objects to make them reactive. On mount, Vue recursively builds a virtual node (vnode) tree and patches it into real DOM using browser APIs like `createElementNS` and `insertBefore`. When reactive state changes, Vue queues a re-render via `queueJob`, batching multiple synchronous changes into a single DOM patch. That is why `updated` fires once even if you change three properties in a row. On unmount, Vue calls `unmountComponent`, removes the vnodes, and stops all effect watchers. ### Common Mistakes **DOM access in `created`** ```js // Wrong created() { this.$refs.box.style.color = 'red'; // TypeError: cannot read properties of undefined } // Fix: move to mounted() mounted() { this.$refs.box.style.color = 'red'; // works } ``` **Missing cleanup on unmount** ```js // Wrong - interval keeps running after component is gone mounted() { this.timer = setInterval(() => fetchUpdates(), 5000); } // Fix beforeUnmount() { clearInterval(this.timer); } ``` **API fetch in `beforeMount`** ```js // Wrong - blocks render, breaks SSR hydration async beforeMount() { this.user = await fetchUser(); // Vue 3 warns on async pre-mount hooks } // Fix: use async setup() or onMounted async setup() { const user = await fetchUser(); return { user }; } ``` **Stale `$refs` in `updated`** ```js // Wrong - child component may not have updated yet updated() { console.log(this.$refs.child.innerHTML); // possibly stale } // Fix updated() { this.$nextTick(() => console.log(this.$refs.child.innerHTML)); // fresh } ``` ### Child vs Parent Hook Order This trips up a lot of developers. When a parent updates a prop, the order is: 1. Parent `beforeUpdate` 2. Child `beforeUpdate` 3. Child `updated` 4. Parent `updated` Parent finishes last. If you read child state from a parent hook, do it in `updated`, not `beforeUpdate`. In dynamic lists like TodoMVC with nested components, getting this order wrong produces subtle stale-data bugs. ### Real-World Usage - **Nuxt.js**: `asyncData` runs in `created` for SSR data fetching before hydration - **Pinia**: `beforeUnmount` removes store subscriptions to prevent duplicate handlers - **Element Plus**: `mounted` initializes tooltip positioning after DOM is available - **Quasar**: `onBeforeUnmount` stops media query listeners in layout components - **Fetch abort pattern**: store `AbortController` in `setup`, cancel in `onBeforeUnmount` ### Follow-Up Questions **Q:** What is the difference between `mounted` and `nextTick`? **A:** `mounted` fires after Vue patches the vnode tree into the DOM, but before the browser paints the frame. `nextTick` waits for the full update queue to flush, including child component updates. If you need to read a child's DOM after a state change, use `nextTick` inside `updated`. **Q:** How does SSR affect lifecycle hooks? **A:** Vue skips `beforeMount` and `mounted` on the server entirely. Only `beforeCreate`, `created`, and `setup` run. Logic that must work on both server and client belongs in `setup` or `created`. **Q:** What is the Composition API equivalent of `beforeCreate`? **A:** There is no direct equivalent. `setup()` itself runs at the same time `beforeCreate` would. Code at the top of `setup`, before any `onX` calls, fills that role. **Q:** When does `activated` fire with KeepAlive? **A:** When a cached component re-enters the view. First time it fires after `mounted`; on subsequent shows it fires instead of `mounted`. Pair `activated` with `deactivated` for data refresh logic on cached routes. **Q:** In a Teleport component inside Suspense, trace the lifecycle from suspend to resolve. **A:** Suspense enters pending state, child `setup`/`created` runs, the async dependency resolves, parent `beforeUpdate` fires, child `mounted` fires (Teleport changes where DOM lands but not when hooks fire), then parent `updated`. The lifecycle order stays intact regardless of Teleport's target. ## Examples ### Basic: Lifecycle hook order in the console ```js // Options API - trace every phase export default { name: 'OrderDemo', data() { return { message: 'hello' }; }, beforeCreate() { console.log('beforeCreate - this.message:', this.message); }, // undefined created() { console.log('created - this.message:', this.message); }, // 'hello' mounted() { console.log('mounted - DOM ready'); }, beforeUpdate() { console.log('beforeUpdate - about to re-render'); }, updated() { console.log('updated - DOM refreshed'); }, beforeUnmount(){ console.log('beforeUnmount - last chance for cleanup'); }, unmounted() { console.log('unmounted'); } }; ``` Change `message` from a parent component. You will see `beforeUpdate` and `updated` fire once per change, not once per property assignment - because Vue batches updates. ### Intermediate: Dashboard card with fetch and cleanup ```vue <template> <div v-if="loading">Loading...</div> <div v-else>{{ user.name }}</div> </template> <script> export default { data() { return { user: null, loading: true, controller: null }; }, async mounted() { this.controller = new AbortController(); try { const res = await fetch('/api/profile', { signal: this.controller.signal }); this.user = await res.json(); } catch (e) { if (e.name !== 'AbortError') console.error(e); } finally { this.loading = false; } }, beforeUnmount() { // User navigates away before fetch completes - cancel it this.controller?.abort(); } }; </script> ``` The `AbortController` pattern prevents "can't set state on unmounted component" warnings in SPAs where users navigate quickly between routes. ### Advanced: Parent-child update ordering edge case ```js // Child logs its own hooks const Child = { props: ['items'], beforeUpdate() { console.log('Child beforeUpdate'); }, updated() { console.log('Child updated'); }, template: '<ul><li v-for="i in items" :key="i">{{ i }}</li></ul>' }; // Parent triggers update by changing items export default { components: { Child }, data() { return { items: [] }; }, mounted() { setTimeout(() => { this.items = [1, 2, 3]; }, 1000); }, beforeUpdate() { console.log('Parent beforeUpdate'); }, updated() { console.log('Parent updated'); }, template: '<Child :items="items" />' }; // Console order: // Parent beforeUpdate // Child beforeUpdate // Child updated // Parent updated ``` Parent's `updated` fires last. If you need to read the child's updated DOM from a parent hook, do it in parent `updated`, not `beforeUpdate`. Reading too early gives you the pre-update state.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.