Lifecycle Hooks
| Vue 2 | Description | Vue 3 |
|---|---|---|
| beforeCreate() | Before creation | setup() |
| created() | After creation | setup() |
| beforeMount() | Before mounting | onBeforeMount() |
| mounted() | After mounting | onMounted() |
| beforeUpdate() | Before update | onBeforeUpdate() |
| updated() | After update | onUpdated() |
| beforeDestroy() | Before destruction | onBeforeUnmount() |
| destroyed() | After destruction | onUnmounted() |
| errorCaptured() | Error capture | onErrorCaptured() |
Priority of v-if and v-for
- In Vue 2.x, v-for has higher priority than v-if.
- In Vue 3.x, v-if has higher priority than v-for.
Ref Arrays in v-for
- Vue 2.x automatically populates ref arrays.
- Vue 3.x requires manual handling.
<ul>
<li v-for='element in elements' :key='element.id' :ref='addRef'>
{{ element.content }}
</li>
</ul>
methods: {
addRef(element) {
this.refList.push(element);
}
}
$children Property
- Vue 2.x: Access child components via
this.$children. - Vue 3.x:
$childrenis removed; use$refsinstead.
<child-component ref='childRef'/>
this.$refs.childRef
Composition API (setup)
Overview
The Composition API introduces setup() for organizing logic in components.
Reactivity System
- Vue 2.x: Uses
Object.defineProperty()for reactivity, which has limitations like inability to detect array changes directly and requiring property iteration. - Vue 3.x: Uses
Proxyfor reactivity, eliminating the need for iteration.
Data Definition
ref(): For primitive data types.reactive(): For complex objects.
Auto-Import Plugin
Simplify imports with unplugin-auto-import.
npm install unplugin-auto-import --save-dev
// vite.config.js
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import AutoImport from 'unplugin-auto-import/vite';
import { resolve } from 'path';
export default defineConfig({
plugins: [
vue(),
AutoImport({ imports: ['vue', 'vue-router'] })
],
resolve: {
alias: { '@': resolve(__dirname, './src') }
}
});
toRefs Utility
Use toRefs() to destructure reactive objects while maintaining reactivity.
Computed Properties
const user = reactive({
firstName: 'John',
lastName: 'Doe',
initials: computed(() => user.firstName.slice(0, 1) + user.lastName.slice(0, 1))
});
const message = ref('Hello');
const trimmedMessage = computed(() => message.value.trim());
const customComputed = computed({
get() {
return message.value.toUpperCase();
},
set(newValue) {
console.log('Value set:', newValue);
}
});
Watch Function
- Vue 2.x:
watch: {
targetObject: {
handler(newValue, oldValue) {
console.log(newValue, oldValue);
},
immediate: true,
deep: true
}
}
- Vue 3.x:
// Watch a single reactive value
watch(dataValue, (newVal, oldVal) => {
console.log(newVal, oldVal);
}, { immediate: true });
// Watch multiple values
watch([firstValue, secondValue], (newVals, oldVals) => {
console.log(newVals, oldVals);
});
// Watch a nested property
watch(() => object.nested.array, (newVal, oldVal) => {
console.log(newVal, oldVal);
});
// Immediate effect watcher
watchEffect(() => {
console.log(dataValue.value);
});
Component Communication
Parent to Child
Parent:
<child-component :message='parentMessage'/>
import ChildComponent from './ChildComponent.vue';
const parentMessage = ref('Data from parent');
Child:
<div>{{ message }}</div>
defineProps({
message: {
type: String,
default: 'Default text'
}
});
Child too Parent
Child:
<div>
{{ count }}
<button @click='updateCount'>Increment</button>
</div>
const count = ref(200);
const emit = defineEmits<{
(e: 'updateCount', value: number): void
}>();
const updateCount = () => {
emit('updateCount', count.value);
};
Parent:
<child-component @updateCount='handleUpdate'/>
import ChildComponent from './ChildComponent.vue';
const handleUpdate = (value) => {
console.log(value);
};
v-model with Custom Props
Parent:
<child-component v-model:quantity='itemQuantity'/>
import ChildComponent from './ChildComponent.vue';
const itemQuantity = ref(1);
Child:
const props = defineProps({
quantity: {
type: Number,
default: 100
}
});
const emit = defineEmits(['update:quantity']);
const modifyQuantity = () => {
emit('update:quantity', 200);
};
Sibling Communication with Event Bus
Install mitt:
npm install mitt --save
Event bus setup:
// bus.js
import mitt from 'mitt';
const eventBus = mitt();
export default eventBus;
Component A:
eventBus.emit('customEvent', data);
Component B:
eventBus.on('customEvent', (eventData) => {
receivedData.value = eventData;
});
Lifecycle in Composition API
In the Composition API, lifecycle hooks are prefixed with 'on', such as onMounted(). Note that beforeCreate and created are not available in setup().
Routing
useRoute()replacesthis.$route.useRouter()replacesthis.$router.
Slots
Default Slot
Parent:
<child-component>Slot content</child-component>
Child:
<div>
<slot></slot>
</div>
Named Slots
Parent:
<child-component>
<template v-slot:header>Header content</template>
<template #footer>Footer content</template>
</child-component>
Child:
<div>
<slot name='header'></slot>
<slot name='footer'></slot>
</div>
Scoped Slots
Parent:
<template #default='{ item }'>
{{ item.name }} - {{ item.age }}
</template>
Child:
<div v-for='element in list' :key='element.id'>
<slot :data='element'></slot>
</div>
Dynamic Slots
Parent:
<template #[slotName]>Dynamic content</template>
const slotName = ref('header');
Teleport Component
Use <teleport> to render content in a different part of the DOM.
<teleport to='#target-container'>Content to teleport</teleport>
Dynamic Components
<component :is='currentComponent'></component>
Async Components
Lazy Loading
Load components only when needed.
<template>
<div ref='observerTarget'>
<AsyncComponent v-if='isVisible'/>
</div>
</template>
import { useIntersectionObserver } from '@vueuse/core';
import { defineAsyncComponent } from 'vue';
const AsyncComponent = defineAsyncComponent(() =>
import('./AsyncComponent.vue')
);
const observerTarget = ref(null);
const isVisible = ref(false);
const { stop } = useIntersectionObserver(
observerTarget,
([{ isIntersecting }]) => {
if (isIntersecting) {
isVisible.value = true;
}
}
);
Suspense
Handle loading states with <Suspense>.
<Suspense>
<template #default>
<AsyncComponent/>
</template>
<template #fallback>
Loading...
</template>
</Suspense>
const AsyncComponent = defineAsyncComponent(() =>
import('./AsyncComponent.vue')
);
Code Splitting
Async components are split in to separate JavaScript files during build.
Mixins
Composition API Mixin
// mixin.js
import { ref } from 'vue';
export default function useCounter() {
const count = ref(1);
const isActive = ref(false);
const increment = () => {
count.value += 1;
isActive.value = true;
setTimeout(() => {
isActive.value = false;
}, 2000);
};
return { count, isActive, increment };
}
// Component.vue
import useCounter from './mixin.js';
const { count, isActive, increment } = useCounter();
Options API Mixin
// mixin.js
export const counterMixin = {
data() {
return {
count: 10
};
},
methods: {
addToCount(value) {
this.count += value;
}
}
};
// Component.vue
import { counterMixin } from './mixin.js';
export default {
mixins: [counterMixin],
data() {
return {
message: 'Hello'
};
}
};
Provide and Inject
Provider:
provide('sharedData', reactiveData);
Injector:
const injectedData = inject('sharedData');
State Management
Vuex
- Access state:
computed(() => store.state.property). - Getters:
computed(() => store.getters.getterName). - Mutations:
store.commit('mutationName'). - Actions:
store.dispatch('actionName'). - Modules: Similar to Vue 2.
- Persistence with
vuex-persistedstate:
npm install vuex-persistedstate --save
import createPersistedState from 'vuex-persistedstate';
export default createStore({
modules: { user },
plugins: [createPersistedState({
key: 'app-storage',
paths: ['user']
})]
});
Pinia
- Differences from Vuex: No mutations, modular by design, smaller size, direct state modification.
- Usage: Define stores with
defineStore(). - Persistence: Use plugins like
pinia-plugin-persistedstate. - Proxy configuration for CORS: Set up in build tools like Vite.