Created
June 11, 2022 17:50
-
-
Save do-adams/173a26cf532db6693ac032e9dd6220bf to your computer and use it in GitHub Desktop.
A Tour of reactive
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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