Unlocking the Power of Reactivity with Vue 3
@oscar_spen
What is Reactivity?
Reactivity is a style of programming that allows us to declaratively react to changes.
The Classic Spreadsheet Example
This doesn't work out of the box in JavaScript.
If we store the sum of two variables and then reassign those variables, the original sum doesn't change.
let x = 5
let y = 7
let sum = x + y
console.log(sum) // sum is 12
y = 19
console.log(sum) // sum is still 12
Imperative vs. Declarative
We can make sure that we always update the sum
value whenever we update one of the operands, but doing so would be doing so imperatively.
Having the ability to declare that the sum
value is always equal to the sum of the two operands is declarative.
let x = 5
let y = 7
let sum = x + y
function updateY(value) {
y = value
sum = x + y
}
console.log(sum) // sum is 12
updateY(19)
console.log(sum) // sum is now 24!
Reactivity in JavaScript
How would we make something reactive in JS?
- Detect when there’s a change in one of the values
- Track the function that changes it
- Trigger the function so it can update the final value
JavaScript Proxies
Proxies allow us to intercept actions performed on objects.
user code </>
target object {}
JavaScript Proxies
Proxies allow us to intercept actions performed on objects.
user code </>
target object {}
Proxy
Proxy Handlers
Handlers define what code should run when object properties are accessed or changed.
let person = {
name: 'Bob Smith',
age: 35
}
let handler = {
get(target, prop) {
return target[prop]
}
}
person = new Proxy(person, handler)
Proxy Handlers—Getters
let person = {
name: 'Bob Smith',
age: 35
}
let handler = {
get(target, prop) {
if (prop === 'age') {
console.warn("It's rude to ask someone's age, but sure.")
}
return target[prop]
}
}
person = new Proxy(person, handler)
console.log(person.name) // Bob Smith
console.log(person.age) // insightful message, then 35
Proxy Handlers—Setters
let handler = {
get(target, prop) {
if (prop === 'age') {
console.warn("It's rude to ask someone's age, but sure.")
}
return target[prop]
}
set(target, prop, value) {
if (prop === 'name' && !value) {
return target[prop] = 'Anonymous'
}
return target[prop] = value
}
}
Proxy Handlers— All Traps
You can specify actions for just about anything that can be done to an Object.
apply construct defineProperty deleteProperty get getOwnPropertyDescriptor getPrototypeOf has isExtensible ownKeys preventExtensions set setPrototypeOf
Building Reactivity
Quick reminder of our three steps to reactivity:
- Detect when there’s a change in one of the values
- Track the function that changes it
- Trigger the function so it can update the final value
Detecting a Change
We don't have to explicitly do this since Proxies allow us to perform an action right when the change happens.
/* the best code
is no code at all */
Tracking a Function
We can track a calling function whenever one of our fields are accessed.
const handler = {
get(target, prop) {
track(target, prop)
return target[prop]
},
set(target, key, value) {
trigger(target, key)
return target[key] = value
}
}
Triggering a Function
We can trigger an effect whenever one of our fields are changed.
const handler = {
get(target, prop) {
track(target, prop)
return target[prop]
},
set(target, key, value) {
trigger(target, key)
return target[key] = value
}
}
A Fully Reactive System
We now have all of the parts we need to register effects and have them be triggered whenever its dependencies are updated.
This yields a great API, since the user can just use objects normally.
Reactivity in Vue
{
data() {
return {
foo: 'Vue'
}
}
)
Vue will recursively create Proxies for the object you return from your data function, and track when there have been changes.
Reactivity Lifecycle in Vue
Formerly known as Vue.observable, this function converts any object into a reactive one.
Vue.reactive
import { reactive } from 'vue'
reactive({
name: 'Bob Smith',
age: 35
})
A Vuex-like State Store
// store.js
const state = Vue.reactive({
foo: 'Vue'
})
const getters = {
theFoo() {
return state.foo
}
}
const actions = {
setFoo(value) {
state.foo = value
}
}
export {
getters,
actions
}
// VueComponent.js
import store from './store.js'
export default {
computed: {
...store.getters
},
methods: {
...store.actions,
doSomethingWithFoo() {
return this.theFoo
}
}
}
Vue.reactive
Vue 3's Extensions of @vue/reactivity
observable -> reactive effect -> watchEffect ref computed watch
ref
Refs make standalone values reactive.
When used in Vue templates, there's no need to access .value
.
import { ref } from 'vue'
const lightsOn = ref(true)
console.log(lightsOn.value) // true
lightsOn.value = false
console.log(lightsOn.value) // false
computed
You can now directly make computed values.
const lightsOn = ref(true)
const msg = computed(() => {
if (lightsOn.value) {
return 'The lights are on!'
} else {
return 'The lights are off.'
}
}
console.log(msg.value) // lights on
lightsOn.value = false
console.log(msg.value) // lights off
watchEffect
const counter = reactive({ num: 0 })
let currentNumber
watchEffect(() => {
currentNumber = counter.num
})
console.log(currentNumber) // 0
counter.num++
console.log(currentNumber) // 1
watchEffect
allows us to register effects that will run whenever the dependencies of that effect change.
watchEffect
const myMap = reactive({ foo: 1 })
let keys
watchEffect(() => {
keys = Object.keys(myMap)
})
console.log(keys) // [ 'foo' ]
myMap.bar = 2
console.log(keys) // [ 'foo', 'bar' ]
It also tracks changes for most of the ways that you can interact with an object, including doing something like listing its keys.
watch
const counter = reactive({ num: 0 })
watch(() => counter.num, (num, oldNum) => {
// do your thing, homie
})
watch
allows us to explicitly declare when an effect should run, via functions or refs directly.
const counter = ref(0)
watch(counter, (num, oldNum) => {
// do what you gotta do
})
Possible uses of Vue's Reactivity module
- Other frontend frameworks
- Interactive animations
- Notifications
- Chat clients
- Databases?
(show everyone your bad gorgeous code)
Oscar Spencer
Software Engineer @
Twitter: @oscar_spen
GitHub: ospencer
grain-lang.org
Unlocking the Power of Reactivity with Vue 3
By Oscar Spencer
Unlocking the Power of Reactivity with Vue 3
While Vue has emerged as a dominant frontend framework, its abilities extend far beyond the DOM. What if we leveraged Vue 3.0’s powerful standalone observability system to manage services or backend datastores, with all of its reactivity goodness? We could build a highly reactive chat app, power a live scoreboard, or even have Vue trigger AWS Lambda functions as app data changes. In this talk, you’ll walk away with a full understanding of observability and the new standalone observable/effect API in Vue 3, and a new outlook on the possible applications of Vue.
- 118