1. Quick Start
Vue.js is a progressive JavaScript framework for building user interfaces. It provides a reactive data-binding system with a component-oriented architecture that makes it straightforward to build everything from simple widgets to complex single-page applications.
Key features:
- Reactive data binding with automatic DOM updates
- Component-based architecture with props, events, and slots
- Template syntax with directives (
v-if,v-for,v-model,v-on,v-bind) - Computed properties and watchers for derived state
- Transition and animation system for enter/leave effects
- Vue 3 Composition API backported (
ref,reactive,computed,watch,setup()) - Single File Components (
.vuefiles) with scoped CSS - Server-side rendering via
vue-server-renderer
Prerequisites: Node.js and a package manager (npm, yarn, or pnpm).
Install and verify:
npm install vue@2.7.16
Minimal example:
<div id="app">{{ message }}</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
<script>
new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
})
</script>
Hello Vue!
Tip
For production, use
vue.min.jsor the runtime-only build (vue.runtime.common.js) for smaller bundle size. The full build includes the template compiler; the runtime build requires pre-compiled templates via a build tool.
What to try next: explore the Template Syntax section for directives, then build your first component.
2. Installation
System Requirements
| Requirement | Minimum |
|---|---|
| Node.js | 12+ (for build tooling) |
| Browser | IE9+ (with polyfills), all modern browsers |
| Package Manager | npm, yarn, or pnpm |
Installation Methods
npm
npm install vue@2.7.16
yarn
yarn add vue@2.7.16
pnpm
pnpm add vue@2.7.16
CDN
<!-- Development (includes warnings) -->
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
<!-- Production (minified) -->
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.min.js"></script>
Build Variants
| Build | File | Includes Compiler | Use Case |
|---|---|---|---|
| Full (UMD) | dist/vue.js |
Yes | CDN, direct <script> |
| Runtime (UMD) | dist/vue.runtime.js |
No | With build tools |
| Full (CommonJS) | dist/vue.common.js |
Yes | Bundlers (older) |
| Runtime (CommonJS) | dist/vue.runtime.common.js |
No | Bundlers (default main) |
| Full (ESM) | dist/vue.esm.js |
Yes | Bundlers (ESM) |
| Runtime (ESM) | dist/vue.runtime.esm.js |
No | Bundlers (default module) |
| Runtime (MJS) | dist/vue.runtime.mjs |
No | Node.js ESM import |
Important
The runtime-only build is approximately 30% lighter than the full build. If you use a build tool (webpack, Vite, Rollup), use the runtime build and let
vue-loaderor@vitejs/plugin-vue2pre-compile your templates. The full build is needed only when compiling templates in the browser at runtime (e.g., passing a string to thetemplateoption).
Post-Install Verification
const Vue = require('vue')
console.log(Vue.version)
2.7.16
Additional Packages
| Package | Install | Purpose |
|---|---|---|
@vue/compiler-sfc |
Included with vue | SFC compilation (.vue files) |
vue-server-renderer |
npm install vue-server-renderer@2.7.16 |
Server-side rendering |
vue-template-compiler |
npm install vue-template-compiler@2.7.16 |
Standalone template compilation |
Warning
The version of
vue-server-rendererandvue-template-compilermust match the version ofvueexactly. A version mismatch causes a startup error:Vue packages version mismatch.
3. Configuration
Vue exposes a global configuration object at Vue.config that controls framework-wide behavior. Set individual properties — do not replace the entire object.
Configuration Reference
| Option | Type | Default | Description |
|---|---|---|---|
silent |
boolean |
false |
Suppress all Vue warnings |
productionTip |
boolean |
true (dev) |
Show production mode tip on boot |
devtools |
boolean |
true (dev) |
Enable Vue DevTools inspection |
performance |
boolean |
false |
Enable component init/compile/render performance tracing in DevTools |
errorHandler |
Function |
null |
Global error handler for uncaught errors in components |
warnHandler |
Function |
null |
Custom handler for Vue warnings |
ignoredElements |
Array<string|RegExp> |
[] |
Ignore custom elements outside Vue (e.g., Web Components) |
keyCodes |
Object |
{} |
Custom key alias(es) for v-on keyboard events |
optionMergeStrategies |
Object |
{} |
Custom merge strategies for component options |
async |
boolean |
true |
Async update batching (set false only for testing) |
// Suppress all warnings
Vue.config.silent = true
// Global error handler
Vue.config.errorHandler = function (err, vm, info) {
console.error(`Error in ${info}:`, err)
}
// Custom warning handler
Vue.config.warnHandler = function (msg, vm, trace) {
console.warn(`[Custom warn]: ${msg}${trace}`)
}
// Ignore Web Components
Vue.config.ignoredElements = ['my-web-component', /^ion-/]
// Custom key aliases for v-on
Vue.config.keyCodes = {
f1: 112,
mediaPlayPause: 179
}
Note
productionTipanddevtoolsare automatically disabled in production builds (whenprocess.env.NODE_ENV === 'production'). SettingVue.config.performance = truerequires a browser that supports theperformance.markAPI.
See the Admin Guide for the complete internal configuration reference and option merge strategy details.
4. Core Usage
Creating a Vue Instance
Every Vue application starts by creating a root Vue instance:
const vm = new Vue({
el: '#app',
data: {
message: 'Hello',
count: 0
},
computed: {
reversedMessage() {
return this.message.split('').reverse().join('')
}
},
methods: {
increment() {
this.count++
}
}
})
Tip
When
elis provided, Vue mounts automatically. Otherwise, callvm.$mount('#app')manually to control mount timing.
Template Syntax
Vue uses an HTML-based template syntax with special directives:
Text Interpolation
<span>{{ message }}</span>
<span v-once>This will never change: {{ message }}</span>
<span v-html="rawHtml"></span>
<span v-text="message"></span>
Warning
v-htmlrenders raw HTML and can expose your application to XSS attacks. Only use it with trusted content — never on user-supplied input.
Directives Reference
| Directive | Purpose | Example |
|---|---|---|
v-bind (:) |
Bind attribute to expression | :href="url" |
v-on (@) |
Attach event listener | @click="handler" |
v-model |
Two-way data binding | v-model="searchText" |
v-if |
Conditional rendering (removes from DOM) | v-if="isVisible" |
v-else-if |
Else-if chain | v-else-if="type === 'B'" |
v-else |
Else block | v-else |
v-show |
Toggle visibility (CSS display) |
v-show="isActive" |
v-for |
List rendering | v-for="item in items" |
v-slot (#) |
Named/scoped slot | v-slot:header |
v-pre |
Skip compilation for this element | v-pre |
v-cloak |
Hide until compiled | v-cloak |
v-once |
Render once, skip future updates | v-once |
Conditional Rendering
<div v-if="type === 'A'">Type A</div>
<div v-else-if="type === 'B'">Type B</div>
<div v-else>Other</div>
<!-- v-show toggles CSS display (element stays in DOM) -->
<div v-show="isVisible">Toggled by CSS</div>
List Rendering
<ul>
<li v-for="(item, index) in items" :key="item.id">
{{ index }}: {{ item.name }}
</li>
</ul>
<!-- Object iteration -->
<div v-for="(value, key, index) in object" :key="key">
{{ key }}: {{ value }}
</div>
Important
Always provide a unique
:keyattribute when usingv-for. Without it, Vue uses an "in-place patch" strategy that can cause subtle bugs with stateful components or transitions.
Event Handling
<!-- Method handler -->
<button @click="increment">+1</button>
<!-- Inline handler -->
<button @click="count++">+1</button>
<!-- Event modifiers -->
<form @submit.prevent="onSubmit">...</form>
<a @click.stop="doThis">Stop propagation</a>
<input @keyup.enter="submit">
<button @click.once="doOnce">Once only</button>
<!-- Key modifiers -->
<input @keyup.enter="submit">
<input @keyup.esc="cancel">
Event modifiers: .stop, .prevent, .capture, .self, .once, .passive
Key modifiers: .enter, .tab, .delete, .esc, .space, .up, .down, .left, .right
System modifiers: .ctrl, .alt, .shift, .meta, .exact
Two-Way Binding with v-model
<!-- Text input -->
<input v-model="message" placeholder="edit me">
<!-- Checkbox -->
<input type="checkbox" v-model="checked">
<!-- Radio -->
<input type="radio" v-model="picked" value="a">
<!-- Select -->
<select v-model="selected">
<option value="">Choose</option>
<option value="A">A</option>
</select>
<!-- Modifiers -->
<input v-model.lazy="msg"> <!-- sync on change instead of input -->
<input v-model.number="age"> <!-- auto-typecast to number -->
<input v-model.trim="msg"> <!-- trim whitespace -->
Reactivity System
Vue's reactivity system uses Object.defineProperty to convert data properties into getter/setter pairs. When a property is accessed during rendering, it is tracked as a dependency. When it changes, dependent watchers are notified and the view updates.
Reactivity caveats — arrays:
Vue intercepts these array mutation methods to trigger reactivity: push, pop, shift, unshift, splice, sort, reverse.
Setting an item by index or changing length directly is not reactive:
// NOT reactive:
this.items[index] = newValue
this.items.length = newLength
// Use instead:
Vue.set(this.items, index, newValue) // or this.$set(...)
this.items.splice(newLength)
Reactivity caveats — objects:
Vue cannot detect property addition or deletion on already-created objects:
// NOT reactive (adding new property):
this.obj.newProp = 'value'
// Use instead:
Vue.set(this.obj, 'newProp', 'value') // or this.$set(...)
// Removing:
Vue.delete(this.obj, 'prop') // or this.$delete(...)
Warning
Vue.setandVue.delete(aliased asthis.$set/this.$delete) are essential when adding or removing properties from reactive objects or setting array items by index. Without them, the UI will not update.
Computed Properties and Watchers
new Vue({
data: {
firstName: 'John',
lastName: 'Doe',
question: ''
},
computed: {
// Getter only
fullName() {
return `${this.firstName} ${this.lastName}`
},
// Getter + setter
fullNameRW: {
get() { return `${this.firstName} ${this.lastName}` },
set(newValue) {
const names = newValue.split(' ')
this.firstName = names[0]
this.lastName = names[names.length - 1]
}
}
},
watch: {
// Simple watcher
question(newVal, oldVal) {
this.fetchAnswer()
},
// Deep watcher
someObject: {
handler(newVal) { /* ... */ },
deep: true
},
// Immediate watcher (fires on creation)
searchQuery: {
handler(val) { this.search(val) },
immediate: true
}
}
})
Components
// Global registration
Vue.component('my-component', {
props: {
title: { type: String, required: true },
count: { type: Number, default: 0 }
},
template: '<div>{{ title }}: {{ count }}</div>'
})
// Local registration
const ChildComponent = {
props: ['message'],
template: '<span>{{ message }}</span>'
}
new Vue({
components: { ChildComponent },
template: '<child-component message="hello" />'
})
Props
props: {
// Type check
age: Number,
// Multiple types
value: [String, Number],
// Required with type
name: { type: String, required: true },
// Default value
count: { type: Number, default: 0 },
// Default function (for objects/arrays)
items: { type: Array, default: () => [] },
// Custom validator
status: {
type: String,
validator: (val) => ['active', 'inactive'].includes(val)
}
}
Events
<!-- Parent -->
<child-component @update="handleUpdate" />
<!-- Child -->
<script>
export default {
methods: {
save() {
this.$emit('update', this.localData)
}
}
}
</script>
Slots
<!-- Parent -->
<layout-component>
<template #header>
<h1>Page Title</h1>
</template>
<template #default>
<p>Main content</p>
</template>
<template #footer="{ year }">
<p>© {{ year }}</p>
</template>
</layout-component>
<!-- Child (LayoutComponent) -->
<div>
<header><slot name="header"></slot></header>
<main><slot></slot></main>
<footer><slot name="footer" :year="currentYear"></slot></footer>
</div>
Lifecycle Hooks
Components go through a defined lifecycle. Use hooks to run code at specific stages:
| Hook | When It Fires |
|---|---|
beforeCreate |
Instance initialized, before data/events setup |
created |
Instance created, data/computed/methods available, no DOM yet |
beforeMount |
Before initial render |
mounted |
DOM rendered, this.$el available |
beforeUpdate |
Data changed, before DOM re-render |
updated |
DOM re-rendered after data change |
beforeDestroy |
Before teardown |
destroyed |
Instance destroyed, all bindings/listeners removed |
activated |
<keep-alive> component activated |
deactivated |
<keep-alive> component deactivated |
errorCaptured |
Error from descendant component captured |
serverPrefetch |
SSR only — resolved before server render |
renderTracked |
Reactive dependency tracked (dev only) |
renderTriggered |
Re-render triggered by dependency change (dev only) |
Filters
Vue 2 supports template filters for text formatting:
// Global filter
Vue.filter('capitalize', function (value) {
if (!value) return ''
return value.charAt(0).toUpperCase() + value.slice(1)
})
// Local filter
new Vue({
filters: {
truncate(v) {
const newline = v.indexOf('\n')
return newline > 0 ? v.slice(0, newline) : v
},
formatDate(v) {
return v.replace(/T|Z/g, ' ')
},
pluralize(n) {
return n === 1 ? 'item' : 'items'
}
}
})
<!-- In mustaches -->
{{ message | capitalize }}
<!-- In v-bind -->
<div :id="rawId | formatId"></div>
<!-- Chained -->
{{ message | capitalize | truncate }}
Custom Directives
// Global directive
Vue.directive('focus', {
inserted(el) {
el.focus()
}
})
// Shorthand (bind + update)
Vue.directive('color', function (el, binding) {
el.style.color = binding.value
})
<input v-focus>
<span v-color="'red'">Colored text</span>
Directive hook functions: bind, inserted, update, componentUpdated, unbind
Plugins
// Using a plugin
Vue.use(MyPlugin, { someOption: true })
// Plugin structure
const MyPlugin = {
install(Vue, options) {
Vue.mixin({ /* ... */ })
Vue.directive('my-directive', { /* ... */ })
Vue.prototype.$myMethod = function () { /* ... */ }
}
}
Note
Vue.use()automatically prevents double-installation of the same plugin. Calling it multiple times with the same plugin has no additional effect.
Mixins
const myMixin = {
created() {
console.log('mixin hook called')
},
methods: {
sharedMethod() { /* ... */ }
}
}
// Local mixin
new Vue({
mixins: [myMixin]
})
// Global mixin (affects ALL instances — use with caution)
Vue.mixin({
created() { /* runs in every component */ }
})
Transitions and Animations
<transition name="fade">
<p v-if="show">Hello</p>
</transition>
<transition-group name="list" tag="ul">
<li v-for="item in items" :key="item.id">{{ item.text }}</li>
</transition-group>
.fade-enter-active, .fade-leave-active {
transition: opacity 0.5s;
}
.fade-enter, .fade-leave-to {
opacity: 0;
}
Vue applies CSS classes during enter/leave transitions:
| Class | When Applied |
|---|---|
{name}-enter |
Starting state for enter |
{name}-enter-active |
Active state during enter |
{name}-enter-to |
Ending state for enter |
{name}-leave |
Starting state for leave |
{name}-leave-active |
Active state during leave |
{name}-leave-to |
Ending state for leave |
Built-in <keep-alive> Component
<keep-alive> caches inactive component instances instead of destroying them:
<!-- Cache all -->
<keep-alive>
<component :is="currentView"></component>
</keep-alive>
<!-- Include/exclude by name -->
<keep-alive include="ComponentA,ComponentB">
<router-view></router-view>
</keep-alive>
<!-- Regex or array -->
<keep-alive :include="/^Todo/">
<component :is="view"></component>
</keep-alive>
<!-- Max cached instances (LRU eviction) -->
<keep-alive :max="10">
<component :is="view"></component>
</keep-alive>
| Prop | Type | Description |
|---|---|---|
include |
String | RegExp | Array |
Only cache matching component names |
exclude |
String | RegExp | Array |
Never cache matching component names |
max |
String | Number |
Maximum number of cached instances |
Cached components receive activated and deactivated lifecycle hooks instead of mounted/destroyed.
5. Common Tasks
Composition API (Vue 2.7)
Vue 2.7 backports the Composition API from Vue 3. Use setup() to organize logic by concern rather than by option type:
import { ref, computed, watch, onMounted, defineComponent } from 'vue'
export default defineComponent({
setup() {
const count = ref(0)
const doubled = computed(() => count.value * 2)
function increment() {
count.value++
}
watch(count, (newVal, oldVal) => {
console.log(`count changed: ${oldVal} → ${newVal}`)
})
onMounted(() => {
console.log('component mounted')
})
return { count, doubled, increment }
}
})
Tip
<script setup>syntax is supported in Vue 2.7 SFCs via@vue/compiler-sfc. It provides a more concise way to use the Composition API.
Composition API Functions Available in Vue 2.7
Reactivity:
| Function | Purpose |
|---|---|
ref(value) |
Create a reactive reference |
shallowRef(value) |
Shallow reactive reference |
computed(getter) |
Computed value |
reactive(object) |
Deep reactive object |
shallowReactive(object) |
Shallow reactive object |
readonly(object) |
Deep readonly proxy |
shallowReadonly(object) |
Shallow readonly proxy |
toRef(obj, key) |
Create ref from reactive property |
toRefs(obj) |
Convert reactive object to plain object of refs |
unref(ref) |
Unwrap ref (returns .value or the value itself) |
isRef(value) |
Check if value is a ref |
isReactive(value) |
Check if value is reactive |
isReadonly(value) |
Check if value is readonly |
isProxy(value) |
Check if value is reactive or readonly |
triggerRef(ref) |
Force trigger a shallow ref |
customRef(factory) |
Create ref with custom track/trigger |
markRaw(object) |
Mark object to never be made reactive |
toRaw(proxy) |
Get the raw object from a reactive proxy |
Watchers:
| Function | Purpose |
|---|---|
watch(source, cb, options?) |
Watch reactive source(s) with callback |
watchEffect(effect) |
Run effect immediately and re-run on dependency change |
watchPostEffect(effect) |
watchEffect with flush: 'post' |
watchSyncEffect(effect) |
watchEffect with flush: 'sync' |
Watch options: immediate (fire on creation), deep (deep watch), flush ('pre' | 'post' | 'sync').
Lifecycle hooks (Composition API):
| Composition API | Options API Equivalent |
|---|---|
onBeforeMount |
beforeMount |
onMounted |
mounted |
onBeforeUpdate |
beforeUpdate |
onUpdated |
updated |
onBeforeUnmount |
beforeDestroy |
onUnmounted |
destroyed |
onActivated |
activated |
onDeactivated |
deactivated |
onErrorCaptured |
errorCaptured |
onServerPrefetch |
serverPrefetch |
onRenderTracked |
renderTracked |
onRenderTriggered |
renderTriggered |
Dependency Injection:
import { provide, inject } from 'vue'
// Parent
setup() {
provide('theme', 'dark')
}
// Child (any depth)
setup() {
const theme = inject('theme', 'light') // default: 'light'
}
Effect Scope:
import { effectScope, onScopeDispose } from 'vue'
const scope = effectScope()
scope.run(() => {
const counter = ref(0)
watch(counter, () => { /* ... */ })
onScopeDispose(() => { console.log('scope disposed') })
})
scope.stop() // disposes all effects in scope
Note
The
suspensibleoption fordefineAsyncComponentis not supported in Vue 2 and is silently ignored. TheuseListeners()helper is Vue 2-only (Vue 3 merged listeners into$attrs).
Provide/Inject (Options API)
// Parent component
export default {
provide() {
return {
theme: this.theme
}
}
}
// Descendant component (any depth)
export default {
inject: ['theme']
}
Async Components
// Simple async component
Vue.component('async-comp', () => import('./MyComponent.vue'))
// With loading/error states (Vue 2.7 defineAsyncComponent)
import { defineAsyncComponent } from 'vue'
const AsyncComp = defineAsyncComponent({
loader: () => import('./HeavyComponent.vue'),
loadingComponent: LoadingSpinner,
errorComponent: ErrorDisplay,
delay: 200, // delay before showing loading (ms)
timeout: 10000, // timeout before showing error (ms)
onError(error, retry, fail, attempts) {
if (attempts <= 3) retry()
else fail()
}
})
Using Vue.extend for Subclasses
const Profile = Vue.extend({
template: '<p>{{ fullName }}</p>',
data() {
return { firstName: 'Jane', lastName: 'Doe' }
},
computed: {
fullName() { return `${this.firstName} ${this.lastName}` }
}
})
// Create an instance
new Profile().$mount('#profile')
Programmatic Event Bus
// Create a shared event bus
const EventBus = new Vue()
// Component A: emit
EventBus.$emit('item-added', { id: 1 })
// Component B: listen
EventBus.$on('item-added', (item) => {
console.log('Received:', item)
})
// Clean up (in beforeDestroy)
EventBus.$off('item-added')
Vue.observable for Lightweight State
const state = Vue.observable({
count: 0,
message: 'hello'
})
// Use in any component
new Vue({
computed: {
count: () => state.count
},
methods: {
increment() { state.count++ }
}
})
Using nextTick for DOM Updates
this.message = 'updated'
this.$nextTick(() => {
// DOM is now updated
console.log(this.$el.textContent) // 'updated'
})
// Or with async/await
async handler() {
this.message = 'updated'
await this.$nextTick()
// DOM updated
}
// Static method
Vue.nextTick(() => { /* ... */ })
Error Handling
// Global error handler
Vue.config.errorHandler = function (err, vm, info) {
// info is a Vue-specific error info, e.g. which lifecycle hook
console.error('Global error:', err, info)
// Report to tracking service
}
// Component-level with errorCaptured
export default {
errorCaptured(err, childVm, info) {
console.error('Caught from child:', err)
return false // stop propagation
}
}
// Composition API
import { onErrorCaptured } from 'vue'
setup() {
onErrorCaptured((err, instance, info) => {
console.error(err)
return false
})
}
Render Functions and h()
// Options API render function
export default {
render(createElement) {
return createElement('div', { class: 'container' }, [
createElement('h1', this.title),
this.$slots.default
])
}
}
// Composition API h()
import { h } from 'vue'
export default {
setup() {
return () => h('div', { class: 'container' }, 'Hello')
}
}
Functional Components
Vue.component('functional-comp', {
functional: true,
props: ['level'],
render(createElement, context) {
return createElement(
`h${context.props.level}`,
context.data,
context.children
)
}
})
6. Troubleshooting
Error: Vue packages version mismatch
Cause: vue-server-renderer or vue-template-compiler version does not match the vue package version.
Solution:
- Check installed versions:
npm ls vue vue-server-renderer vue-template-compiler - Align all versions to the same release:
npm install vue@2.7.16 vue-server-renderer@2.7.16 vue-template-compiler@2.7.16
Prevention: Pin all three packages to the same exact version in package.json.
Error: Property or method "X" is not defined on the instance
Cause: You referenced a property in the template that is not declared in data, computed, methods, or props.
Solution:
- Verify the property name matches exactly (case-sensitive)
- Ensure the property is declared in the correct option (
data,computed, ormethods) - If using Composition API, ensure you return the property from
setup()
Prevention: Always declare all template-referenced properties in your component definition.
Error: Avoid mutating a prop directly
Cause: A child component is modifying a prop passed from its parent.
Solution:
- Use a local
dataproperty initialized from the prop:data() { return { localValue: this.initialValue } } - Or use a computed property to transform the prop:
computed: { normalizedValue() { return this.value.trim().toLowerCase() } }
Prevention: Follow one-way data flow — props down, events up.
Error: Do not replace the Vue.config object
Cause: Code is assigning a new object to Vue.config rather than setting individual properties.
Solution: Set properties individually:
// Wrong:
Vue.config = { silent: true }
// Right:
Vue.config.silent = true
Warning: Event "X" is emitted but the handler is registered for "Y"
Cause: Case mismatch between emitted event name and the listener in a template. HTML attributes are case-insensitive, so camelCase event names don't work in in-DOM templates. (GitHub issue #13287)
Solution: Use kebab-case for event names:
// Emit
this.$emit('item-selected', data)
<!-- Listen -->
<child @item-selected="handleSelect" />
Reactivity not working — Vue.set / this.$set needed
Cause: Adding a new property to an already-reactive object or setting an array item by index does not trigger updates because Object.defineProperty can only intercept existing properties.
Solution:
// Objects: use Vue.set
this.$set(this.obj, 'newKey', 'value')
// Arrays: use Vue.set or splice
this.$set(this.items, index, newValue)
this.items.splice(index, 1, newValue)
Warning: Vue is a constructor and should be called with the new keyword
Cause: Calling Vue() as a function instead of new Vue().
Solution: Always use the new keyword:
const vm = new Vue({ /* options */ })
SFC compilation error: SFC contains no <script> tags
Cause: @vue/compiler-sfc's compileScript() was called on a .vue file that has no <script> block.
Solution: Ensure your .vue file contains a <script> or <script setup> block. If the component is template-only, add a minimal script block:
<script>
export default {}
</script>
<keep-alive> not caching components
Cause: The component is missing a name option, or the include/exclude pattern does not match the component's registered name.
Solution:
- Add a
nameoption to cached components - Verify
include/excludepatterns match the component name string - Check that
maxis not set too low, causing LRU eviction
Source maps broken in Firefox
Cause: Known issue with Vue 2 source map generation in certain browser debugging scenarios. (GitHub issue #12689)
Solution: Use Chrome DevTools for debugging, or disable source maps in your build configuration if Firefox debugging is critical.
Race condition in reactive updates
Cause: Potential race condition reported in the reactivity system. (GitHub issue #13309)
Solution: Ensure reactive mutations are performed synchronously within the same tick. Avoid concurrent async operations that mutate the same reactive data without serialization.
Memory leaks with large component trees
Cause: Components or watchers not being properly cleaned up. (GitHub issue #13308)
Solution:
- Clean up event listeners and timers in
beforeDestroy - Remove event bus listeners:
EventBus.$off('event', this.handler) - Cancel pending async operations in
beforeDestroy
7. FAQ
Q: What is the difference between Vue 2 and Vue 3?
Vue 3 introduced Proxy-based reactivity (replacing Object.defineProperty), the Composition API, <Teleport>, <Suspense>, improved TypeScript support, and multiple root nodes in templates (fragments). Vue 2.7 backports the Composition API but retains the Object.defineProperty-based reactivity system. Vue 2 reached End of Life on December 31, 2023.
Q: Can I use the Composition API in Vue 2?
Yes. Vue 2.7 includes native support for setup(), ref, reactive, computed, watch, provide/inject, lifecycle hooks (onMounted, etc.), and <script setup> in SFCs. No additional plugin is needed.
Q: When should I use v-if vs v-show?
Use v-if when the condition rarely changes — it has higher toggle cost (adds/removes elements from DOM) but no initial render cost for hidden elements. Use v-show when toggling frequently — it has higher initial render cost (always renders) but very cheap toggle cost (CSS only).
Q: How do I register a component globally vs locally?
Global: Vue.component('name', definition) — available in all templates. Local: add to the components option of the parent component. Prefer local registration to keep bundle size down with tree-shaking.
Q: What is Vue.extend used for?
Vue.extend creates a reusable component constructor (a "subclass" of Vue). It is used internally by Vue.component when you pass a plain object, and can be used directly for programmatic component instantiation (e.g., modals, toasts).
Q: How do I handle errors from async components?
Use defineAsyncComponent with an onError callback. The callback receives the error, retry() and fail() functions, and the attempt count, letting you implement retry logic.
Q: What is Vue.observable and when should I use it?
Vue.observable(obj) makes a plain object reactive — useful for lightweight shared state across components without Vuex. Available since Vue 2.6.
Q: How does nextTick work?
Vue.nextTick(cb) / this.$nextTick(cb) defers the callback until after the next DOM update cycle. Vue batches data changes and updates the DOM asynchronously, so use nextTick when you need to read or manipulate the DOM immediately after a data change.
Q: Can I use TypeScript with Vue 2?
Yes. Vue 2.7 ships TypeScript type definitions at types/index.d.ts. Use defineComponent() to enable proper type inference for component options. <script setup lang="ts"> is supported in SFCs with @vue/compiler-sfc.
Q: How do provide and inject work?
provide makes data available to all descendant components (any depth). inject retrieves that data. In the Options API, use them as component options. In the Composition API (Vue 2.7), import provide() and inject() from vue and call them in setup(). The prototype chain is used internally — child provides shadow parent provides.