Frontend Development 12 min read

Understanding Vue 3 ref vs reactive: Internal Mechanisms and Why ref Is Recommended

This article explains the internal implementations of Vue 3's ref and reactive APIs, compares their behavior, highlights the limitations of reactive, and demonstrates why using ref provides a more uniform, flexible, and deep‑reactive solution for frontend developers.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Understanding Vue 3 ref vs reactive: Internal Mechanisms and Why ref Is Recommended

In Vue 3, ref and reactive are the core APIs for creating reactive data. ref wraps primitive values while reactive handles objects and arrays, and the official documentation recommends using ref for most cases.

Internal Working Principle of ref

The ref function returns a reactive, mutable reference object that contains a .value property pointing to the internal value.

export function ref(value?: unknown) {
  return createRef(value, false)
}

export function shallowRef(value?: unknown) {
  return createRef(value, true)
}

function createRef(rawValue: unknown, shallow: boolean) {
  if (isRef(rawValue)) {
    return rawValue
  }
  return new RefImpl(rawValue, shallow)
}

class RefImpl
{
  private _value: T
  private _rawValue: T
  public dep?: Dep = undefined
  public readonly __v_isRef = true
  constructor(value: T, public readonly __v_isShallow: boolean) {
    this._rawValue = __v_isShallow ? value : toRaw(value)
    this._value = __v_isShallow ? value : toReactive(value)
  }
  get value() {
    trackRefValue(this)
    return this._value
  }
  set value(newVal) {
    const useDirectValue = this.__v_isShallow || isShallow(newVal) || isReadonly(newVal)
    newVal = useDirectValue ? newVal : toRaw(newVal)
    if (hasChanged(newVal, this._rawValue)) {
      this._rawValue = newVal
      this._value = useDirectValue ? newVal : toReactive(newVal)
      triggerRefValue(this, DirtyLevels.Dirty, newVal)
    }
  }
}

The ref function creates a new RefImpl instance, which provides getter and setter methods for dependency tracking and update triggering. Any data type can be declared as reactive state using ref , including objects and arrays.

import { ref } from 'vue'

let state = ref({ count: 0 })
state.value.count++

Internal Working Principle of reactive

The reactive function accepts an object and returns a reactive proxy created with Proxy .

function reactive(target) {
  if (target && target.__v_isReactive) {
    return target
  }
  return createReactiveObject(
    target,
    false,
    mutableHandlers,
    mutableCollectionHandlers,
    reactiveMap
  )
}

function createReactiveObject(target, isReadonly, baseHandlers, collectionHandlers, proxyMap) {
  if (!isObject(target)) {
    return target
  }
  const existingProxy = proxyMap.get(target)
  if (existingProxy) {
    return existingProxy
  }
  const proxy = new Proxy(target, baseHandlers)
  proxyMap.set(target, proxy)
  return proxy
}

Unlike ref , reactive works by proxying the original object, which intercepts operations to achieve reactivity.

Limitations of reactive

reactive is only effective for reference types (objects, arrays, Map, Set). Primitive types such as string , number , and boolean remain non‑reactive when passed to reactive .

import { reactive } from 'vue'
const state = reactive({ count: 0 })

Improper usage can also cause loss of reactivity, for example:

Directly assigning a new object to a reactive variable.

Replacing a reactive object with another reactive call.

Destructuring a reactive object without toRefs , which yields non‑reactive values.

Assigning a reactive property to a plain variable.

// Direct assignment loses reactivity
let state = reactive({ count: 0 })
state = { count: 1 } // no longer reactive

// Destructuring without toRefs
const state = reactive({ count: 0 })
let { count } = state // count is not reactive

Why ref Is Recommended

ref offers a uniform API for all data types, providing deep reactivity for nested objects and arrays while also supporting shallow reactivity via shallowRef . This simplifies code and avoids the pitfalls of reactive .

import { ref } from 'vue'

let num = ref(0)
let str = ref('Hello')
let obj = ref({ count: 0 })

num.value++
str.value += ' World'
obj.value.count++

For complex updates, ref allows straightforward replacement of the whole object or selective property changes while preserving reactivity.

import { ref } from 'vue'

let state = ref({ count: 0, name: 'Vue' })
state.value = { count: 10, name: 'Vue 4' }
state.value.count = 20
state.value.name = 'Vue 5'
state.value.newProperty = 'New Property'
delete state.value.newProperty
let { count, name } = state.value
state.value = { count: count + 1, name }

Conclusion

In Vue 3, ref provides a more unified, flexible, and deep‑reactive solution compared to reactive , avoiding many of the latter's limitations. Developers are encouraged to prefer ref for declaring reactive state.

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