Vue’s reactivity system is built on JavaScript Proxies and allows you to automatically track changes to data and update the DOM accordingly.
Reactive State with ref()
The ref() function from packages/reactivity/src/ref.ts:58-65 creates a reactive reference to a value:
import { ref } from 'vue'
const count = ref ( 0 )
console . log ( count . value ) // 0
count . value ++
console . log ( count . value ) // 1
API Signature
The initial value to wrap in a ref
A reactive ref object with a .value property
How ref() Works
From packages/reactivity/src/ref.ts:103-108, ref() creates a RefImpl instance:
class RefImpl < T = any > {
_value : T
private _rawValue : T
dep : Dep = new Dep ()
public readonly __v_isRef = true
public readonly __v_isShallow : boolean = false
constructor ( value : T , isShallow : boolean ) {
this . _rawValue = isShallow ? value : toRaw ( value )
this . _value = isShallow ? value : toReactive ( value )
this . __v_isShallow = isShallow
}
get value () {
this . dep . track () // Track dependencies
return this . _value
}
set value ( newValue ) {
// Detect changes and trigger updates
if ( hasChanged ( newValue , this . _rawValue )) {
this . _rawValue = newValue
this . _value = this . __v_isShallow ? newValue : toReactive ( newValue )
this . dep . trigger () // Trigger effects
}
}
}
Using Refs in Templates
Refs are automatically unwrapped in templates:
< template >
< div >
<!-- No need for .value in template -->
< p > Count: {{ count }} </ p >
< button @ click = " count ++ " > Increment </ button >
</ div >
</ template >
< script setup >
import { ref } from 'vue'
const count = ref ( 0 )
</ script >
In templates, refs are automatically unwrapped. In JavaScript code, you must use .value to access or modify the ref’s value.
Reactive State with reactive()
The reactive() function from packages/reactivity/src/reactive.ts:91-105 creates a deep reactive proxy of an object:
import { reactive } from 'vue'
const state = reactive ({
count: 0 ,
nested: {
value: 'hello'
}
})
console . log ( state . count ) // 0
state . count ++
console . log ( state . count ) // 1
// Nested properties are also reactive
state . nested . value = 'world'
API Signature
The source object to make reactive
A reactive proxy of the object with deeply unwrapped nested refs
How reactive() Works
From packages/reactivity/src/reactive.ts:261-302:
function createReactiveObject (
target : Target ,
isReadonly : boolean ,
baseHandlers : ProxyHandler < any >,
collectionHandlers : ProxyHandler < any >,
proxyMap : WeakMap < Target , any >
) {
if ( ! isObject ( target )) {
if ( __DEV__ ) {
warn ( `value cannot be made reactive: ${ String ( target ) } ` )
}
return target
}
// Target already has corresponding Proxy
const existingProxy = proxyMap . get ( target )
if ( existingProxy ) {
return existingProxy
}
const proxy = new Proxy ( target , baseHandlers )
proxyMap . set ( target , proxy )
return proxy
}
The reactive conversion is “deep”: it affects all nested properties.
ref() vs reactive()
ref() - For Primitives
reactive() - For Objects
import { ref } from 'vue'
// Best for primitive values
const count = ref ( 0 )
const message = ref ( 'hello' )
const isActive = ref ( true )
// Access via .value
console . log ( count . value )
count . value ++
When to use which:
Use ref() for primitive values (numbers, strings, booleans)
Use ref() when you need to reassign the entire value
Use reactive() for objects and arrays
Use ref() consistently for a uniform API
Additional Reactive APIs
shallowRef()
From packages/reactivity/src/ref.ts:90-101, creates a ref that tracks its own .value change but doesn’t make the value reactive:
import { shallowRef } from 'vue'
const state = shallowRef ({ count: 1 })
// Triggers reactivity
state . value = { count: 2 }
// Does NOT trigger reactivity
state . value . count = 3
shallowReactive()
From packages/reactivity/src/reactive.ts:142-152, creates a shallow reactive proxy:
import { shallowReactive } from 'vue'
const state = shallowReactive ({
foo: 1 ,
nested: { bar: 2 }
})
// Reactive
state . foo ++
// NOT reactive
state . nested . bar ++
readonly()
From packages/reactivity/src/reactive.ts:208-218, creates a readonly proxy:
import { reactive , readonly } from 'vue'
const original = reactive ({ count: 0 })
const copy = readonly ( original )
// Works
original . count ++
// Warns and fails
copy . count ++
Utility Functions
isRef()
From packages/reactivity/src/ref.ts:45-49, checks if a value is a ref:
import { ref , isRef } from 'vue'
const count = ref ( 0 )
const plain = 0
console . log ( isRef ( count )) // true
console . log ( isRef ( plain )) // false
unref()
From packages/reactivity/src/ref.ts:231-233, returns the value if it’s a ref, otherwise returns the value itself:
import { ref , unref } from 'vue'
function useFoo ( x : number | Ref < number >) {
const unwrapped = unref ( x )
// unwrapped is guaranteed to be number now
}
toRef()
From packages/reactivity/src/ref.ts:465-496, creates a ref from a reactive object property:
import { reactive , toRef } from 'vue'
const state = reactive ({ foo: 1 , bar: 2 })
const fooRef = toRef ( state , 'foo' )
// Mutating the ref updates the original
fooRef . value ++
console . log ( state . foo ) // 2
// Mutating the original also updates the ref
state . foo ++
console . log ( fooRef . value ) // 3
toRefs()
From packages/reactivity/src/ref.ts:345-354, converts all properties of a reactive object to refs:
import { reactive , toRefs } from 'vue'
const state = reactive ({
foo: 1 ,
bar: 2
})
const stateAsRefs = toRefs ( state )
// stateAsRefs.foo is now a ref linked to state.foo
stateAsRefs . foo . value ++
console . log ( state . foo ) // 2
toRefs is useful when returning a reactive object from a composable function, so consumers can destructure without losing reactivity:export function useMouse () {
const state = reactive ({ x: 0 , y: 0 })
// ... logic
return toRefs ( state )
}
// Destructuring works!
const { x , y } = useMouse ()
toValue()
From packages/reactivity/src/ref.ts:251-253, normalizes values/refs/getters to values:
import { ref , toValue } from 'vue'
toValue ( 1 ) // 1
toValue ( ref ( 1 )) // 1
toValue (() => 1 ) // 1
Reactivity Utilities
isReactive()
From packages/reactivity/src/reactive.ts:323-328, checks if an object is reactive:
import { reactive , readonly , isReactive } from 'vue'
const state = reactive ({ count: 0 })
console . log ( isReactive ( state )) // true
const plain = { count: 0 }
console . log ( isReactive ( plain )) // false
isReadonly()
From packages/reactivity/src/reactive.ts:342-344, checks if an object is readonly:
import { reactive , readonly , isReadonly } from 'vue'
const original = reactive ({ count: 0 })
const copy = readonly ( original )
console . log ( isReadonly ( copy )) // true
console . log ( isReadonly ( original )) // false
isProxy()
From packages/reactivity/src/reactive.ts:359-361, checks if an object is a reactive or readonly proxy:
import { reactive , readonly , isProxy } from 'vue'
console . log ( isProxy ( reactive ({}))) // true
console . log ( isProxy ( readonly ({}))) // true
toRaw()
From packages/reactivity/src/reactive.ts:387-390, returns the raw object:
import { reactive , toRaw } from 'vue'
const foo = {}
const reactiveFoo = reactive ( foo )
console . log ( toRaw ( reactiveFoo ) === foo ) // true
markRaw()
From packages/reactivity/src/reactive.ts:416-420, marks an object to never be converted to a proxy:
import { reactive , markRaw , isReactive } from 'vue'
const foo = markRaw ({})
console . log ( isReactive ( reactive ( foo ))) // false
// Also works when nested
const bar = reactive ({ foo })
console . log ( isReactive ( bar . foo )) // false
Use markRaw() with caution. It allows you to selectively opt-out of reactive conversion, which can lead to reactivity issues if not used properly.
Computed Properties Derive reactive state with computed()
Watchers React to state changes with watch()
Lifecycle Hooks Execute code at specific lifecycle stages