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?

  1. Detect when there’s a change in one of the values
  2. Track the function that changes it
  3. 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:

  1. Detect when there’s a change in one of the values
  2. Track the function that changes it
  3. 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

Made with Slides.com