Coding Patterns: Bridging Vue 2 and Vue 3
1. Project Creation
Vue 2 (using Vue CLI):
vue create my-app
cd my-app
npm run serve
Vue 3 (using create‑vue):
npm create vue@latest
cd my-app
npm run dev
During initialisation you are prompted to add TypeScript, Router, Pinia, testing tools, and linter formatters.
2. Component Template
Vue 2 – Options API
<template>
<div></div>
</template>
<script>
export default {
props: {},
data() {
return {
// local state
};
},
created() {},
methods: {},
mounted() {},
watch: {},
computed: {},
components: {}
};
</script>
<style scoped></style>
Vue 3 – Composition API (<script setup>)
<template>
<div>{{ greeting }}</div>
</template>
<script setup>
import { ref, reactive, toRefs, toRef, computed, watch, watchEffect } from 'vue';
// reactive primitive
const greeting = ref('Hello');
// reactive object
const user = reactive({ name: 'Alice', age: 30 });
// destructure reactive object keeping reactivity
const { name } = toRefs(user);
const age = toRef(user, 'age');
// computed (read‑only)
const upperGreeting = computed(() => greeting.value.toUpperCase());
// writable computed
const fullName = computed({
get: () => user.name + ' (read)',
set: (val) => { user.name = val; }
});
// watcher
watch(user, (newVal, oldVal) => {
console.log('user changed', newVal, oldVal);
}, { deep: true });
// watchEffect – auto‑detect dependencies
watchEffect(() => {
console.log('current user name:', user.name);
});
</script>
<style scoped></style>
3. Lifecycle Hooks
| Vue 2 (Options API) | Vue 3 (Composition API) |
|---|---|
beforeCreate |
not needed (setup replaces it) |
created |
not needed (setup) |
beforeMount |
onBeforeMount |
mounted |
onMounted |
beforeUpdate |
onBeforeUpdate |
updated |
onUpdated |
beforeDestroy |
onBeforeUnmount |
destroyed |
onUnmounted |
errorCaptured |
onErrorCaptured |
Vue 2 example:
export default {
mounted() { console.log('mounted'); }
}
Vue 3 example:
import { onMounted } from 'vue';
onMounted(() => console.log('mounted'));
4. Component Communication
4.1 Parent → Child (props)
Vue 2
<!-- parent -->
<Child :items="list" :callback="onCallback" />
<!-- child -->
<script>
export default {
props: ['items', 'callback']
}
</script>
Vue 3
<!-- parent -->
<Child :items="list" :callback="onCallback" />
<!-- child -->
<script setup>
defineProps(['items', 'callback'])
</script>
4.2 Child → Parent (custom events)
Vue 2
<!-- child -->
<button @click="$emit('update', payload)">Update</button>
Vue 3
<!-- child -->
<script setup>
const emit = defineEmits(['update']);
emit('update', payload);
</script>
4.3 Event Bus
Vue 2 – global bus on prototype
Vue.prototype.$bus = new Vue();
// emit
this.$bus.$emit('my-event', data);
// listen
this.$bus.$on('my-event', handler);
Vue 3 – external library mitt
npm install mitt
// emitter.ts
import mitt from 'mitt';
export const emitter = mitt();
// usage
emitter.emit('my-event', data);
emitter.on('my-event', handler);
4.4 Two‑way binding (v‑model)
Vue 2 – single v-model bound to value and input
<!-- parent -->
<Child v-model="keyword" />
<!-- child -->
<input :value="value" @input="$emit('input', $event.target.value)">
Vue 3 – multiple v-model bindings possible, default prop modelValue
<!-- parent -->
<Child v-model:title="pageTitle" v-model:content="body" />
<!-- child -->
<script setup>
const props = defineProps(['modelValue', 'title', 'content'])
const emit = defineEmits(['update:modelValue', 'update:title', 'update:content'])
</script>
.sync modifier from Vue 2 is removed in Vue 3; use v-model: instead.
4.5 Provide / Inject
Vue 2
// ancestor
provide() { return { api: this.api } }
// descendant
inject: ['api']
Vue 3
// ancestor
import { provide } from 'vue';
provide('api', api);
// descendant
import { inject } from 'vue';
const api = inject('api');
4.6 State Management
Vue 2 – Vuex
- Store:
new Vuex.Store({ state, mutations, actions, getters }) - Covered by
mapState,mapActionshelpers.
Vue 3 – Pinia
- Define:
defineStore('counter', { state, getters, actions }) - Access:
const store = useCounterStore(); store.increment() - Reactivity preserving:
storeToRefs(store)to destructuring.
5. Routing
Installation
- Vue 2:
npm install vue-router@3 - Vue 3:
npm install vue-router@4
Router setup
| Aspect | Vue 2 | Vue 3 |
|---|---|---|
| Instance | new VueRouter({ routes }) |
createRouter({ history: createWebHashHistory(), routes }) |
| Mode | mode: 'history' inside options |
alen history: createWebHistory() |
| Navigation | this.$router.push('/about') |
import { useRouter } from 'vue-router'; router.push('/about') |
| Dynamic segments | { path: '/user/:id' } – same in both |
same |
| Nested routes | children: [...] – same |
same |
| Route props | props: true (params) or props: route => route.query |
same |
| Redirect | redirect: '/home' – same |
same |
Link and navigation guards are95% identical.
6. Composables (Replace Mixins)
In Vue 2 reusable logic is often shared via mixins. Vue 3 encourages extracting pure functions (composables).
encoding example
// composables/useMouse.ts
import { ref, onUnmounted } from 'vue';
export function useMouse() {
const x = ref(0);
const y = ref(0);
const update = (e: MouseEvent) => {
x.value = e.pageX;
y.value = e.pageY;
};
window.addEventListener('mousemove', update);
onUnmounted(() => window.removeEventListener('mousemove', update));
return { x, y };
}
console.log
7. UI Frameworks
- Element – Vue 2:
element‑ui| Vue 3:element‑plus - Vant – Vue 2:
vant@2| Vue 3:vant@3(orvant@next) - Vuetify – Vue 2:
vuetify@2| Vue 3:vuetify@3 - Naive UI – designed for Vue 3 only
- View UI (iview) – Vue 2:
view‑ui| Vue 3:view‑ui‑plus
Always check the library’s documentation for the correct version to your Vue release.