Frontend Development 25 min read

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.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Vue 2.0 vs Vue 3.0: Core Differences, Reactive System, Composition API, Lifecycle Hooks and Practical Code Examples

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 → unmounted

Vue 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;

$el

is 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;

this

can 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 object

isProxy, isReactive, isReadonly

Method

Purpose

Typical Return

isProxy

Detect any proxy created by

reactive

or

readonly
true

for

reactive(obj)

or

readonly(obj)
isReactive

Detect a reactive proxy (including readonly‑wrapped reactive)

true

for

reactive(obj)

or

readonly(reactive(obj))
isReadonly

Detect a readonly proxy

true

for

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 } // ✅ update

defineProps 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) // ReferenceError

Temporal Dead Zone (TDZ) causes a ReferenceError when accessing the variable before its declaration.

console.log(typeof value) // ReferenceError
let value = 1

Arrow 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) // undefined

Object 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.

frontendJavaScriptVueComposition APIVue3Vue2
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.