DEV Community

Cover image for Vue.js Reactivity Fundamentals : Composition API
Sonay Kara
Sonay Kara

Posted on

Vue.js Reactivity Fundamentals : Composition API

Reactivity Fundamentals

Declaring Reactive State

If you want to specify the reactive state in the Composition API, you can use the recommended way ref() function.

Example :



import { ref } from 'vue'

const count = ref(0)


Enter fullscreen mode Exit fullscreen mode

ref() takes the argument and returns it wrapped in a ref object. This object has the .value property. We can use .value to access the reactive value

Example :



const count = ref(0)

console.log(count) // { value: 0 }
console.log(count.value) // 0

count.value++
console.log(count.value) // 1


Enter fullscreen mode Exit fullscreen mode

As seen in the example, we can access the reactive {value: 0} object with console.log(count). We see that the reactive variable defined with Count is an object. as a result ref() takes the argument and returns it wrapped in a ref object.

If you want to access references in a component's template, simply declare and return them from a component setup() function.



import { ref } from 'vue'

export default {
  setup() {
    const count = ref(0)
    return {
      count
    }
  }
}


Enter fullscreen mode Exit fullscreen mode


<div>{{ count }}</div>


Enter fullscreen mode Exit fullscreen mode

When using setup, there is no need to use .value to access the reactive variable.

Note : setup is a special hook dedicated to the Composition API. I will explain the setup hook in another article.

You can also modify a reference directly in event handlers



<button @click="count++">
  {{ count }}
</button>


Enter fullscreen mode Exit fullscreen mode

In this case, it may have occurred to you that for more complex logic, we may not define functions for event handlers. Yes we can do this



import { ref } from 'vue'

export default {
  setup() {
    const count = ref(0)

    function increment() 
      count.value++
    }
    return {
      count,
      increment
    }
  }
}


Enter fullscreen mode Exit fullscreen mode

Now let's use this function



<button @click="increment">
  {{ count }}
</button>


Enter fullscreen mode Exit fullscreen mode

If you want a test, codepen

Script Setup

We mentioned the setup at the beginning of the article. Now we will talk about script setup, which is a different and more useful method. Instead of using setup, you can use script setup.



<script setup>
import { ref } from 'vue'

const count = ref(0)

function increment() {
  count.value++
}
</script>

<template>
  <button @click="increment">
    {{ count }}
  </button>
</template>


Enter fullscreen mode Exit fullscreen mode

Why Refs?

You might be wondering why we need references with .value instead of plain variables. To explain this we need to examine how Vue's reactivity system works.

When you use a reference in a template and then change the value of the reference, Vue automatically detects the change and updates the DOM accordingly. When a component is first created, Vue keeps track of every reference used during creation. Then when a reference mutates, it will trigger a re-render for the components that follow it.

The .value property gives Vue the opportunity to detect when a ref has been accessed or mutated. Under the hood, Vue performs the tracking in its getter, and performs triggering in its setter. Conceptually, you can think of a ref as an object that looks like this.



const myRef = {
  _value: 0,
  get value() {
    track()
    return this._value
  },
  set value(newValue) {
    this._value = newValue
    trigger()
  }
}


Enter fullscreen mode Exit fullscreen mode

Deep Reactivity

Refs can hold any value type, including deeply nested objects, arrays, or JavaScript built-in data structures like Map.

A ref will make its value deeply reactive. This means you can expect changes to be detected even when you mutate nested objects or arrays



import { ref } from 'vue'

const obj = ref({
  nested: { count: 0 },
  arr: ['foo', 'bar']
})

function mutateDeeply() {
  obj.value.nested.count++
  obj.value.arr.push('baz')
}


Enter fullscreen mode Exit fullscreen mode

reactive()

There is another way to declare reactive status with the Reactive() API. Unlike ref, which wraps the inner value into a custom object, reactive() makes the object itself reactive



import { reactive } from 'vue'

const state = reactive({ count: 0 })


Enter fullscreen mode Exit fullscreen mode

Usage in template:



<button @click="state.count++">
  {{ state.count }}
</button>


Enter fullscreen mode Exit fullscreen mode

Vue is able to intercept the access and mutation of all properties of a reactive object for reactivity tracking and triggering.

Limitations of reactive()

The reactive() API has several limitations

  • It cannot hold primitive types such as string, number or boolean.

  • Cannot replace entire object: since Vue's reactivity tracking works over property access, we must always keep the same reference to the reactive object. This means we can't easily "replace" a reactive object because the reactivity connection to the first reference is lost



let state = reactive({ count: 0 })

// the above reference ({ count: 0 }) is no longer being tracked
// (reactivity connection is lost!)
state = reactive({ count: 1 })


Enter fullscreen mode Exit fullscreen mode
  • Not destructure-friendly: when we destructure a reactive object's primitive type property into local variables, or when we pass that property into a function, we will lose the reactivity connection:

Due to these limitations, it is recommended to use ref() as the primary API for declarative reactive status

Conclusion

In this article, we examined the reactivity of vue.js Composition API. We learned why reactivity should be used and examined several usage methods.

Top comments (0)