DEV Community

YPChen
YPChen

Posted on • Updated on

Break Vue's reactivity

This article won't do a deep dive into the implementation of Vue.js reactivity, I just want to show some examples that will break the reactivity when you are writing with the framework.

Should you be interested in the mechanism of the reactivity under the Vue.js, here are some information I think that would help:

  1. Reactivity in depth | Vue.js
  2. Vue 3 Reactivity | Vue Mastery
  3. Vue JS 3 Reactivity Fundamentals - Composition API by Marius Espejo

TL;DR

  • We cannot use toRefs to modify a ref's value that stands for a primitive value
  • Destructuring props breaks its reactivity.
  • Mutable methods are totally acceptable when using Vue.

Two Way Binding

Let's start with a simple input element with a reset button, we can see what we have typed down below the input element.

It works! Not surprising, right? But in reality, we frequently separate elements into child components to make the function of each component clearer. So, let's move the input element and the reset button to Comp.vue. And it seems Vue is arguing about the prop are read-only.

It's good to see Vue prohibit modifying props "directly", because it usually causes data flow of the states to become a mess. BUT you have noticed that I double quoted the "directly" right? By using toRefs, we can transform and destructure properties in the props to refs that is belong to the child component. And...it fails, it turned out you still cannot modify a ref's value that is "primitive". Just "primitive", let's make our ref-- msg, to become an object: ref({ greetings: 'Hello World!' }), and it's work again!


We cannot use toRefs to modify a ref's value that stands for a primitive value, but we can change the primitive values of the properties in a ref that acts for an object.


How about to destructure the props deeper to the msg that have we type less codes without the object but just its property-- greetings. Let's type something in the input, it worked...before clicking the "Resume to default" button. (I also add styles to stand out the elements 😁)


Destructuring props breaks its reactivity. Though we use toRefs, the ref in the child component is no longer synchronized with the ref in the parent component via props.


Immutation and Mutation

For this part, let's play both mutable and immutable method on the object ref to see how we can break the reactivity.

Again, on List.vue, clicking "Add to ordered list" button will trigger a function that is trying to mutate the value of a ref that is an array itself, but not the element in it, which cause a warning pops up.

As what we learned previously, putting the orderedList as a property of lists' ref solves the issue. However, there is another way-- mutable methods. By means of the methods, I will not change what the value property under the ref points to, so I do not need to put unorderedList into the lists object to add items into the list.

Unlike states which are all immutable in React, the Vue doc mentions that mutation on ref is valid-- Reactivity API: Core #ref | Vue.js.

Yet currently the functions to add items to or clear the content of the lists in List.vue are a little bit duplicated. Using dependency injection, we can make the functions more flexible. And the reactivity breaks again...

In the case of assigning a new array to the argument list, we didn't change the lists.value.ol points to, but the reference of the argument list:

Here is an example provided by the people in Vue community to show the scope of the argument.

let a = [];
function add(b) {
    b = [...b, 1];
    console.log(b) // [1]
}
add(a);
console.log(a); // []
Enter fullscreen mode Exit fullscreen mode

Though it's okay to do:

function addToList(item) {
  lists.value.ol = [...lists.value.ol, item];
}
Enter fullscreen mode Exit fullscreen mode

or

// pass the whole lists
function addToList(lists, item) {
  lists.ol = [...lists.ol, item];
}
Enter fullscreen mode Exit fullscreen mode

For flexibility, I would choose to use mutable methods.

Just one more thing...

I don't know the actual mechanism, but it seems Vue triggers the effects when the value of a ref is changed, and then to update the peer dependencies of refs in the component.

What I know is that Vue uses "signal" to have the reactivity, and its concept may refer to observer pattern

The example below use shallowRef instead of ref for the lists. You can see both add to list button don't make changes on the screen. But if you then click the "restore to default" button, the added items show up suddenly.

It's also work without shallowRef but a plain object. And it won't do so if the functions in the List.vue no longer use the msg.

Thanks for reading 🙏

Top comments (0)