Vue 2.0 vs Vue 3.0: Core Differences, Reactive System, Composition API, Lifecycle Hooks and Practical Code Examples
This article provides a comprehensive comparison between Vue 2 and Vue 3, covering changes in reactivity implementation, the introduction of the Composition API, lifecycle hook updates, component caching, data binding techniques, state‑management alternatives, storage options, and numerous code snippets that illustrate each concept for developers preparing for interviews or upgrading projects.
Preface
The author resigned on 2025‑02‑27 and received an offer on 2025‑03‑21; this article records the interview questions encountered and summarizes the answers.
Vue 2.0 vs Vue 3.0 Differences
Reactivity Re‑implementation
1. Vue 3 replaces Object.defineProperty with Proxy for deep property tracking.
With Object.defineProperty , Vue hijacks the whole object, performs a deep traversal of all properties and defines a getter and setter for each property.
With Proxy , Vue intercepts the whole object without deep traversal, but still needs to define getter , setter and deleteProperty to achieve reactivity.
new Proxy(data, {
// intercept property read
get(target, prop) { return Reflect.get(target, prop) },
// intercept property write or addition
set(target, prop, value) { return Reflect.set(target, prop, value) },
// intercept property deletion
deleteProperty(target, prop) { return Reflect.deleteProperty(target, prop) }
})2. Added the Composition API for better logic reuse and code organization.
3. Updated the priority of v‑if and v‑for .
5. Supports multiple root nodes; template no longer requires a single root element.
6. Tree‑shaking: functions such as ref , reactive , computed are only bundled when used.
Compilation Optimizations
Vue 2 marks static nodes to speed up diff.
Vue 3 marks and hoists all static nodes, so the diff only compares dynamic nodes.
Static‑hoist (hoistStatic) moves static nodes out of the render function, creating them once at app start.
Patch flags are added to dynamic nodes, allowing the virtual‑DOM diff to skip unchanged parts.
Cache handlers to avoid recreating functions on every update.
Lifecycle Changes
Vue 3 retains most Vue 2 hooks but renames two of them:
beforeDestroy → beforeUnmount
destroyed → unmountedVue 2.x
Vue 3.x
Explanation
beforeCreate
setup()
Data observation and event initialization have not started;
cannot access data, computed, watch, methods
.
created
setup()
Instance is created, options are resolved, but the render result is not yet mounted;
$elis unavailable.
beforeMount
onBeforeMount
Called before mounting; template is compiled but not yet inserted into the DOM.
mounted
onMounted
DOM is replaced with the compiled HTML; AJAX can be performed here.
beforeUpdate
onBeforeUpdate
Reactive data changed but the real DOM has not been patched yet.
updated
onUpdated
Component DOM has been updated; avoid mutating state here as it may cause infinite loops. This hook is not called during SSR.
beforeDestroy
onBeforeUnmount
Instance is still fully usable;
thiscan be accessed.
destroyed
onUnmounted
All bindings are removed, listeners are cleared, child instances are destroyed; not called during SSR.
Component Two‑Way Data Binding
Basic usage before Vue 3.4:
<template>
<input :value="props.modelValue" @input="emit('update:modelValue', $event.target.value)" />
</template>
<script setup>
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
</script>For a custom component with multiple bound properties:
<template>
<objRange v-model:range="range" v-model:area="area" />
</template>
<script setup>
import { ref } from 'vue'
const range = ref([])
const area = ref([])
const props = defineProps({
range: { type: Array, default: () => [] },
area: { type: Array, default: () => [] }
})
const emit = defineEmits(['update:range', 'update:area'])
</script>Composition API vs Options API
Options API groups code by options (data, methods, computed, watch).
Composition API groups code by logical functionality .
Options API reuses logic via mixins (prone to naming conflicts).
Composition API reuses logic via custom hooks, offering better maintainability.
Options API accesses component state with this ; Composition API does not use this inside setup , exposing state via returned objects.
$nextTick
Vue’s reactivity is asynchronous; $nextTick ensures code runs after the DOM has been updated.
keep‑alive
Built‑in component used to cache dynamic components or route components, preventing destruction on navigation.
<template>
<keep-alive>
<router-view/>
</keep-alive>
</template>Additional lifecycle hooks activated and deactivated fire when a cached component is shown or hidden.
Attributes include and exclude control which components are cached.
Why key in Vue Lists
Keys allow Vue’s virtual DOM diff to identify moved or changed items efficiently; without keys Vue falls back to “in‑place reuse”.
MVVM vs MVC
Model – data source.
View – UI rendering.
Controller – user interaction handling.
ViewModel – binds Model and View with automatic updates; MVVM decouples View from Model, unlike MVC which requires manual DOM updates.
ref, unref, isRef, toRef, toRefs, toRaw
// Define reactive variables
const name1 = ref('name1') // ref
const name2 = 'name2' // plain
const obj = reactive({ name: 'name3' })
console.log(isRef(name1), isRef(name2), isRef(obj)) // true false false
console.log(unref(name1), unref(name2), unref(obj)) // name1 name2 {name:'name3'}
const name3 = toRef(obj, 'name') // single property ref
const { name5: name } = toRefs(obj) // all properties as refs
const rawData = toRaw(reactiveData) // get original objectisProxy, isReactive, isReadonly
Method
Purpose
Typical Return
isProxyDetect any proxy created by
reactiveor
readonly truefor
reactive(obj)or
readonly(obj) isReactiveDetect a reactive proxy (including readonly‑wrapped reactive)
truefor
reactive(obj)or
readonly(reactive(obj)) isReadonlyDetect a readonly proxy
truefor
readonly(obj)ref vs shallowRef vs reactive vs shallowReactive
// ref (deep reactive)
const refValue = ref({ count: 0 })
refValue.value.count++ // triggers update
// shallowRef (only .value is tracked)
const shallowRefValue = shallowRef({ count: 0 })
shallowRefValue.value.count++ // ❌ no update
shallowRefValue.value = { count: 100 } // ✅ update
// reactive (deep)
const reactiveObj = reactive({ nested: { count: 0 } })
reactiveObj.nested.count++ // ✅ update
// shallowReactive (only first level)
const shallowReactiveObj = shallowReactive({ nested: { count: 0 } })
shallowReactiveObj.nested.count++ // ❌ no update
shallowReactiveObj.nested = { count: 1 } // ✅ updatedefineProps Parameters
defineProps({
theme: {
type: String,
default: 'dark',
required: true,
validator: (value) => ['dark', 'light'].includes(value)
}
})Suspense Usage
<template>
<Suspense>
<template #default>
<AsyncComponent />
</template>
<template #fallback>
<div>Loading...</div>
</template>
</Suspense>
</template>
<script setup>
import { defineAsyncComponent } from 'vue'
const AsyncComponent = defineAsyncComponent(() => import('./AsyncComponent.vue'))
</script>v‑slotted Selector
<style scoped>
::v-slotted(.container) {
background-color: lightyellow;
border: 1px solid #ffcc00;
padding: 15px;
}
</style>Pinia vs Vuex
Pinia is composition‑API‑first, more concise; Vuex is options‑API‑based.
Pinia stores are modular by default; Vuex requires manual module division.
Pinia has better TypeScript support.
Pinia is smaller in bundle size.
Pinia allows direct state mutation; Vuex enforces mutations.
localStorage, cookie, sessionStorage Differences
Size: Cookie ~4 KB, Storage ~5 MB.
Lifetime: Cookie has expiration, localStorage is permanent, sessionStorage lasts for the session.
Cookie is sent with each HTTP request; Storage stays client‑side.
Cookie can be scoped by path; Storage is scoped to the origin.
Storage provides dedicated APIs; Cookie does not.
Array Deduplication Methods
// Method 1
const arr1 = [...new Set(originalArr)]
// Method 2 (fails for NaN)
const arr2 = originalArr.filter((item, index) => originalArr.indexOf(item) === index)
// Method 3
const arr3 = originalArr.reduce((acc, cur) => acc.includes(cur) ? acc : [...acc, cur], [])Object Copy Methods
// Shallow copy
const obj1 = { ...originalObj }
const obj2 = Object.assign({}, originalObj)
// Deep copy (no functions)
const deep1 = JSON.parse(JSON.stringify(originalObj))
// Recursive deep clone
function deepClone(original) {
if (original === null || typeof original !== 'object') return original
const clone = Array.isArray(original) ? [] : {}
for (let key in original) {
if (original.hasOwnProperty(key)) {
clone[key] = deepClone(original[key])
}
}
return clone
}Array Intersection, Union, Difference
let arr2 = [1,2,3,4,5]
let arr3 = [3,4,1,2]
// Intersection
console.log(arr2.filter(item => arr3.includes(item)))
// Union
console.log(Array.from(new Set([...arr2, ...arr3])))
// Difference arr2 - arr3
console.log(arr2.filter(item => !arr3.includes(item)))
// Difference arr3 - arr2
console.log(arr3.filter(item => !arr2.includes(item)))Array Flatten
function flatter(arr) {
if (!arr.length) return
return arr.reduce((pre, cur) => Array.isArray(cur) ? [...pre, ...flatter(cur)] : [...pre, cur], [])
}
let arr = [1,2,[1,[2,3,[4,5,[6]]]]]
console.log(flatter(arr))CSS Centering Techniques
/* Flexbox */
.container { display:flex; justify-content:center; align-items:center; }
/* Absolute + transform */
.container { position:relative; }
.child { position:absolute; top:50%; left:50%; transform:translate(-50%,-50%); }
/* Absolute + margin auto */
.child { position:absolute; top:0; right:0; bottom:0; left:0; margin:auto; }
/* Table layout */
.container { display:table-cell; vertical-align:middle; text-align:center; }
.child { display:inline-block; }let and const
Both introduce block‑level scope; variables are not hoisted and cannot be redeclared.
if (false) {
let value = 1
}
console.log(value) // ReferenceErrorTemporal Dead Zone (TDZ) causes a ReferenceError when accessing the variable before its declaration.
console.log(typeof value) // ReferenceError
let value = 1Arrow Functions
No own this ; inherits from the nearest non‑arrow function.
Cannot be used with call , apply , bind .
No arguments object.
Cannot be instantiated with new .
No prototype.
var Foo = () => {}
console.log(Foo.prototype) // undefinedObject Traversal
For‑of iterates values, for‑in iterates keys and traverses the prototype chain (slow). Use Object.keys , Object.values or Object.entries for safe iteration.
Summary
for…in is designed for object key traversal and should not be used for arrays; for…of works with arrays, array‑like objects, strings, Set, Map, and generators.
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.