Skip to content

Instantly share code, notes, and snippets.

@do-adams
Created June 11, 2022 17:50
Show Gist options
  • Save do-adams/173a26cf532db6693ac032e9dd6220bf to your computer and use it in GitHub Desktop.
Save do-adams/173a26cf532db6693ac032e9dd6220bf to your computer and use it in GitHub Desktop.
A Tour of reactive
// A Tour of reactive()
import { reactive, ref, watch } from 'vue'
/**
reactive() - Type Definition:
function reactive<T extends object>(target: T): UnwrapNestedRefs<T>
**/
// reactive() returns a reactive object (an ES Proxy) that has the argument set as a target
const person = reactive({ name: 'Damian' })
person.name === 'Damian' // true
// Primitives - number, bigint, boolean, string, null, undefined, symbol
reactive(0)
reactive(1234567890123456789012345678901234567890n)
reactive(true)
reactive('hello world')
reactive(null)
reactive(undefined)
reactive(Symbol('id'))
/**
Primitives are not valid arguments for reactive(). It only accepts object types.
In development mode, passing a primitive will lead to a console warning and the original argument being returned.
Development mode output below:
'value cannot be made reactive: 0'
'value cannot be made reactive: 1234567890123456789012345678901234567890'
'value cannot be made reactive: true'
'value cannot be made reactive: hello world'
'value cannot be made reactive: null'
'value cannot be made reactive: undefined'
'value cannot be made reactive: Symbol(id)'
0
1234567890123456789012345678901234567890n
true
'hello world'
null
undefined
Symbol(id)
**/
// Objects - Supported object types: Object, Array, Set, Map, WeakMap, WeakSet
let shape = { type: 'circle', color: 'blue', coordinates: { x: 0, y: 0, z: 0 } }
let reactiveShape = reactive(shape)
/**
reactive() returns a (reactive) Proxy object. When dumping a Proxy into the console, it will include an array with the target and handler, respectively:
Proxy [
{
type: 'circle',
color: 'blue',
coordinates: { x: 0, y: 0, z: 0 }
},
{
get: ƒ get(),
set: ƒ set(),
deleteProperty: ƒ deleteProperty(),
has: ƒ has(),
ownKeys: ƒ ownKeys()
}
]
**/
// The reactive Proxy is NOT the same target object
shape === reactiveShape // false
// Although it has access to values on the target object
shape.type === reactiveShape.type // true
shape.color === reactiveShape.color // true
// Any changes made to the target object will be reflected in the Proxy and vice versa
shape.type = 'triangle'
shape.type === reactiveShape.type // true
reactiveShape.color = 'red'
reactiveShape.color === shape.color // true
// Let's add a side effect to reactiveShape
// 'flush: sync' will run the effect immediately on every change
watch(reactiveShape, () => console.log('shape has changed'), { flush: 'sync' })
// You should always use the reactive Proxy to trigger effects on state changes
// otherwise any reactive effects will not work
reactiveShape.color = 'green' // 'shape has changed' logged to console
shape.color = 'purple'
// Property addition and deletion are tracked!
reactiveShape.x = 0 // 'shape has changed' logged to console
delete reactiveShape.x // 'shape has changed' logged to console
// Nested objects are also Proxies (it's *reactive* turtles all the way down!)
reactiveShape.coordinates
/**
Proxy [
{ x: 0, y: 0, z: 0 },
{
get: ƒ get(),
set: ƒ set(),
deleteProperty: ƒ deleteProperty(),
has: ƒ has(),
ownKeys: ƒ ownKeys()
}
]
**/
shape.coordinates === reactiveShape.coordinates // false
shape.coordinates.x === reactiveShape.coordinates.x // true
shape.coordinates.y === reactiveShape.coordinates.y // true
shape.coordinates.z === reactiveShape.coordinates.z // true
// reactive() with reactive()
// reactive() returns the same Proxy for the same object argument
reactiveShape === reactive(shape) // true
// Calling reactive() with a reactive object will return the same reactive object
reactiveShape === reactive(reactiveShape) // true
// reactive() with ref()
// Passing a Ref to reactive() will result...
let countRef = ref(0)
let reactiveCount = reactive(countRef)
/**
Proxy [
RefImpl {
__v_isShallow: false,
dep: undefined,
__v_isRef: true,
_rawValue: 0,
_value: 0,
__proto__: { constructor: ƒ RefImpl(), value: 0 }
},
{
get: ƒ get(),
set: ƒ set(),
deleteProperty: ƒ deleteProperty(),
has: ƒ has(),
ownKeys: ƒ ownKeys()
}
]
**/
// ...in a Proxy that targets our Ref object
reactiveCount === countRef // false
reactiveCount.value === countRef.value // true
// Objects may include nested Refs
let counter = { count: countRef, max: 10, min: 0 }
/**
{
count: RefImpl {
__v_isShallow: false,
dep: undefined,
__v_isRef: true,
_rawValue: 0,
_value: 0,
__proto__: { constructor: ƒ RefImpl(), value: 0 }
},
max: 10,
min: 0
}
**/
let reactiveCounter = reactive(counter)
/**
Proxy [
{
count: RefImpl {
__v_isShallow: false,
dep: undefined,
__v_isRef: true,
_rawValue: 0,
_value: 0,
__proto__: { constructor: ƒ RefImpl(), value: 0 }
},
max: 10,
min: 0
},
{
get: ƒ get(),
set: ƒ set(),
deleteProperty: ƒ deleteProperty(),
has: ƒ has(),
ownKeys: ƒ ownKeys()
}
]
Notice how count is still a Ref object
**/
// Refs within reactive objects are automatically unwrapped (no need to use .value) when accessed
reactiveCounter.count === 0 // true
reactiveCounter.count === countRef.value // true
// New Ref properties on the reactive object are unwrapped too!
reactiveCounter.interval = ref(1)
reactiveCounter.interval === 1 // true
// Ref unwrapping is done by the Proxy, the original counter object still needs to use .value
reactiveCounter.interval === counter.interval // false
reactiveCounter.interval === counter.interval.value // true
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment