Vue Reactivity System is more powerful than you think

Ezra Lazuardy
Level Up Coding
Published in
6 min readMar 27, 2022

--

Vue 3 with its composition API and enhanced reactivity system would make web developers smile. I’m not joking.

Lately, I’ve been using Vue for my project, starting from developing apps for college assignments to the needs of freelancers as web developers. I’m addicted to Vue by now.

Apart from the simplicity and progressive principle that underlies Vue.js, this framework gives you a lot of features for frontend development. The main part that took an important role is the Reactivity System.

Why Reactive?

Frontend development is simple. In a nut case, you just need to display the data you got from the backend, whether it’s dynamic or static. The rest is just to make the interface look ✨eye-catchy✨ and the experience is robust for the user. At least, the word “fine to use” is enough.

Using vanilla js to handle the logic is ok, but it’s all fun and games until you need to handle dynamic data that need to be updated on view when some callbacks, conditions, or events occurred. Simply put, this is what you would do to write a simple counter app with plain vanilla javascript.

With vanilla js, we need a lot of boilerplate code just to make things work. We have to wait for the DOM to be ready, query the DOM component, set up the onclick event, and then update the view manually when an event occurred.

Yes, I know that the sample is oversimplified, but imagine that you have complex apps with complex features and logic. It’s too much effort to do simple yet obvious things, isn’t it?

Now let’s see how we implement the same functionality using Vue (with Vite and Typescript).

By the way, the sample above is written using Vue’s Composition API. It’s the successor of the previous Option API that was introduced in the early version of Vue. TLDR; Composition API introduces a new way to write Vue in a more robust, simple, and less callback mess API. I’ll explain the difference between these APIs in another article.

Notice in the src/components/Counter.vue I’ve added the same counter functionality with this setup:

<script ... >
...

const count = ref(0);
</script>
<template>
...
<span>{{ count }}</span>
<primary-button title="-" @click="count--" />
<primary-button title="+" @click="count++" />
<danger-button title="Reset" @click="count = 0" />
</template>

Using ref with the @click event binding is enough to do the job. Vue’s reactivity system will handle the rest.

Simple yet fun. But guess what? refis a gigachad. It can do some powerful stuff beyond your thinking. Go look at the file in src/components/Video.vue

<script ... >  
...
const video = ref<HTMLVideoElement>();

onMounted(async () => {
await setupVideo(video);
video.value.play();
});

async function setupVideo(video: HTMLVideoElement) {
video.value.width = 640;
video.value.height = 320;
video.value.allowfullscreen = true;
video.value.controls = true;
video.value.type = "video/webm";
video.value.src = " ... ";
}
</script>
<template>
<video ref="video" />
</template>

With the Composition API, ref can auto-bind the DOM element with static typing in nearly 0 configurations. You just need to declare the ref variables with the same name as the HTML ref tag name and the element type. Most of the DOM type is inherited from HTMLElement so using that type can do the work.

In the previous version of Vue, you can access the ref via this.$refs. This method is not recommended anymore in Vue 3, especially when you use Typescript to avoid the static typing issue.

I’m not done yet. ref is overworked yet underpaid. But before I continue to advanced stuff, it’s time to talk about state management theory in Vue.

Meet The State Manager

Photo by CartoonNetworkLA on Tenor

If you’ve tried some frontend framework (e.g. Vue, React, etc.), hearing the “State Management” word is a common thing. But if you don’t, I’ll explain it simply.

Technically, every Vue component instance already “manages” its reactive state. As explained in the sample above, we’ve used the ref to manage the component instance state.

A “component” is a self-contained unit with the following parts:

  • The state is the source of truth that drives our app.
  • The view is a declarative mapping of the state.
  • The actions are possible ways the state could change in reaction to user inputs from the view.
A simple representation of the concept of “one-way data flow”:

Based on the given simple counter sample, the action is when the user clicks an increment, decrement, or reset button. The state is the count variable and the view is the count text that is displayed to the user.

This act is called “State Management”.

However, the simplicity of state management starts to break down when we have multiple components that share a common state:

  • Multiple views may depend on the same piece of state.
  • Actions from different views may need to mutate the same piece of state.

State Management with Reactivity API

Photo by CartoonNetworkLA on Tenor

Sometimes, as your app grows bigger, you need to manage the state in your global app and not just at the component level state. This means that each component can get or mutate the state in the global context of your app.

Implementing this functionality is such a breeze with Vue’s Reactivity API. If you have a piece of state that should be shared by multiple component instances, you can use ref to create a reactive object, and then import it from multiple components.

To implement such functionalities in Vue, you just need to make a store instance like below:

// store.ts
import { ref } from 'vue'

export const store = ref({
count: 0,
increment() {
this.count++
}
})

And apply it in the Vue component.

<!-- ComponentA.vue -->
<script setup lang="ts">
import { store } from './store'
</script>

<template>
<button @click="store.increment()">
From A: {{ store.count }}
</button>
</template>

Or in the other Vue components.

<!-- ComponentB.vue -->
<script setup lang="ts">
import { store } from './store'
</script>
<template>
<button @click="store.increment()">
From B: {{ store.count }}
</button>
</template>

Now whenever the store is mutated, both <ComponentA> and <ComponentB> will update their views automatically. We have a single source of truth now. This also means any component importing store can mutate it however they want, e.g. by calling increment() in the store object.

Note the button’s click handler uses store.increment() with the parenthesis. This is necessary to call the method with the proper this context since it's not a component method.

The State Management Libraries

While the state management using Reactivity API solution will suffice in simple scenarios, there are many more things to consider in large-scale production applications:

  • Stronger conventions for team collaboration.
  • Integrating with the Vue DevTools, including timeline, in-component inspection, and time-travel debugging.
  • Hot Module Replacement.
  • Server-Side Rendering support.
Photo by turtlepirate223 on Tenor

Considering the needs above, you can use Vue’s state management libraries such as Pinia or Vuex.

Pinia is a state management library that implements all of the needs above. It is maintained by the Vue core team and works with both Vue 2 and Vue 3. Existing users may be familiar with Vuex, the previous official state management library for Vue.

Conclusion

Using ref Reactivity API as state manager is such a breeze in a simple scenario. And for the complex usage, HMR, and SSR support, we can use other Vue’s state managers such as Pinia or Vuex. No problem with that.

The Vue’s Reactivity API is simple and fun to use. However, there is a lot more to talk about the Reactivity API. I can’t wait to make another article about this.

--

--

“An idiot admires complexity. A genius admires simplicity.” — Terry A Davis