Vue 3 Composition API: Ref or Reactive





Now, as I write this article, we are getting closer to the release of Vue 3. In my opinion, the most interesting thing is to observe how other developers will perceive and use it. I've had the opportunity to play around with Vue 3 in the past few months, but I know there are those who haven't.



The most significant innovation in the new version is the Composition API. It provides an alternative approach to creating components and is very different from the existing Options API. It's not hard for me to admit that when I first saw him, I didn't understand him. However, as it was applied, meaning began to emerge. You may not be rewriting your entire application using the Composition API, but this article will give you the opportunity to think about how component creation and composition will work.







I've done a couple of presentations recently, and a common question was when I use Ref, and when Reactive, to declare a reactive property. I didn't have a good answer and took a couple of weeks to find the answer, and this article is the result of my research.



I would like to note, moreover, that the above is my opinion and please do not think that it is necessary to do โ€œonly this way and not otherwiseโ€. I plan to use Ref and Reactive this way until someone advises otherwise, or until I figure out a better approach myself. I think it takes some time to understand any new technology, and then proven techniques may appear.



Before proceeding, I assume that you have at least briefly familiarized yourself with the composition API and understand what it consists of. This article focuses on the differences between ref and reactive rather than the API composition mechanism.



Vue 2 reactivity



To get you up to speed, I'll take a quick look at how to create reactive properties in a Vue 2 application. When you want Vue 2 to track changes in properties, you need to declare them in the object returned by the data function.



<template>
  <h1>{{ title }}</h1>
</template>

<script>
  export default {
    data() {
      return {
        title: "Hello, Vue!"
      };
    }
  };
</script>


Under the hood of Vue 2, Object.defineProperty () is called for each property to create a getter and setter to track changes. This is the simplest explanation of the process and I want to convey the idea: there is no magic in it. You can't create reactive properties anywhere and expect Vue to track changes to them. You need to set reactive properties in the data function.



REF and REACTIVE



When working with the Options API, we need to follow some rules when declaring reactive properties, the same with the Composition API. You can't just create a property and expect reactivity. In the following example, I have declared the title property and the setup () function returns it, thereby making it available to the template.



<template>
  <h1>{{ title }}</h1>
</template>

<script>
  export default {
    setup() {
      let title = "Hello, Vue 3!";
      return { title };
    }
  };
</script>




This will work, but the title property will not be reactive. Those. if someone changes the title, those changes will NOT be reflected in the House. For example, if you change the title after 5 seconds, the code below will NOT change the Home.



<template>
  <h1>{{ title }}</h1>
</template>

<script>
  export default {
    setup() {
      let title = "Hello, Vue 3!";

      setTimeout(() => {
        title = "THIS IS A NEW TITLE";
      }, 5000);

      return { title };
    }
  };
</script>




We can import ref and use it to make the property reactive. Under the hood, Vue 3 will create a Proxy .



<template>
  <h1>{{ title }}</h1>
</template>

<script>
  import { ref } from "vue";

  export default {
    setup() {
      const title = ref("Hello, Vue 3!");

      setTimeout(() => {
        //   ,   .value ...
        //   
        title.value = "New Title";
      }, 5000);

      return { title };
    }
  };
</script>




I want to clarify that speaking of Ref and Reactive, I think there are two different cases. The first is to create a component like in the example above and you need reactive properties. The second is when you create functions that allow composition to be used in components and functions. We will discuss both scenarios in this article.



REF



When creating properties of simple types, ref () is your first choice. It's not a silver bullet, but it's worth starting with. Let me remind you of seven primitive types in JavaScript:



  • String
  • Number
  • BigInt
  • Boolean
  • Symbol
  • Null
  • Undefined




import { ref } from "vue";

export default {
  setup() {
    const title = ref("");
    const one = ref(1);
    const isValid = ref(true);
    const foo = ref(null);
  }
};




In the previous example, our title is of type String, so to make the property reactive, we select ref (). If the code below is causing you some questions don't worry, I had the same questions.



import { ref } from "vue";

export default {
  setup() {
    const title = ref("Hello, Vue 3!");

    setTimeout(() => {
      title.value = "New Title";
    }, 5000);

    return { title };
  }
};




Why are we using const if the title will change? Why not use let? If you print title to the console, you might expect to see Hello, Vue 3 !, but instead it will display an object:



{_isRef: true}
value: (...)
_isRef: true
get value: ฦ’ value()
set value: ฦ’ value(newVal)
__proto__: Object




The ref () function will take an argument and return a reactive and mutable ref object. The Ref object has one property, value, which refers to the argument. This means that if you want to get or change the value, you will have to use title.value, and since this is an object that will not change, I declared it const.



Calling REF



The next question is why we don't call title.value in the template?



<template>
  <h1>{{ title }}</h1>
</template>




When Ref is returned as a property in the rendering context (the object returned by the setup () function) and is referenced in the template, Ref automatically returns value. You don't need to add .value in the template.



Computed properties work the same way, inside the setup () function, refer to them as .value.




REACTIVE



We just saw how ref () can be used to make properties of simple types reactive. What if we want to create a reactive object? We could still use ref (), but under the hood Vue will use reactive (), so I'll stick with reactive ().



On the other hand, reactive () will not work with primitive types. The reactive () function takes an object and returns the reactive proxy of the original. This is equivalent to .observable () in Vue 2, and this function name has been changed to avoid confusion with observables in RxJS.



import { reactive } from "vue";

export default {
  setup() {
    const data = reactive({
      title: "Hello, Vue 3"
    });

    return { data };
  }
};




The main difference is how we refer to the reactive object in the template. In the previous example, data is an object containing a title property. You will need to refer to it in the template like this - data.title:



<template>
  <h1>{{ data.title }}</h1>
</template>

<script>
  import { ref } from "vue";

  export default {
    setup() {
      const data = ref({
        title: "Hello, Vue 3"
      });

      return { data };
    }
  };
</script>




Difference between REF and REACTIVE in a component



Based on what we have discussed, the answer would seem to suggest itself? We use ref () for simple types, and reactive () for objects. When I started creating components, it turned out that this is always not the case, and the documentation says:



The difference between using ref and reactive can be, to some extent, comparable to how you write standard program logic in JavaScript.




I pondered this phrase and came to the following conclusions. As the application grew, I got the following properties:



export default {
  setup() {
    const title = ref("Hello, World!");
    const description = ref("");
    const content = ref("Hello world");
    const wordCount = computed(() => content.value.length);

    return { title, description, content, wordCount };
  }
};




In JavaScript, I would understand that these are all properties of my page. In this case, I would group them into a page object, why not do the same now.



<template>
  <div class="page">
    <h1>{{ page.title }}</h1>
    <p>{{ page.wordCount }}</p>
  </div>
</template>

<script>
  import { ref, computed, reactive } from "vue";

  export default {
    setup() {
      const page = reactive({
        title: "Hello, World!",
        description: "",
        content: "Hello world",
        wordCount: computed(() => page.content.length)
      });

      return { page };
    }
  };
</script>




This is my approach to Ref or Reactive, but it would be nice to have your opinion. Are you doing the same? Maybe this is not the right approach? Please comment.



Composition logic



You cannot go wrong when using ref () or reactive () in your components. Both functions will create reactive data, and if you understand how to access it in your setup () function and in templates, there is no problem.



When you start writing composition functions, you need to understand the difference. I use examples from RFC documentation, as some illustrate the nuances well.



You need to create logic for tracking the mouse position. You should also be able to use this same logic in any component when needed. You create a composition function that keeps track of the x and y coordinates and then returns them to the client code.



import { ref, onMounted, onUnmounted } from "vue";

export function useMousePosition() {
  const x = ref(0);
  const y = ref(0);

  function update(e) {
    x.value = e.pageX;
    y.value = e.pageY;
  }

  onMounted(() => {
    window.addEventListener("mousemove", update);
  });

  onUnmounted(() => {
    window.removeEventListener("mousemove", update);
  });

  return { x, y };
}




If you want to use this logic in a component and call this function, then destructure the returned object and then return the x and y coordinates to the template.



<template>
  <h1>Use Mouse Demo</h1>
  <p>x: {{ x }} | y: {{ y }}</p>
</template>

<script>
  import { useMousePosition } from "./use/useMousePosition";

  export default {
    setup() {
      const { x, y } = useMousePosition();
      return { x, y };
    }
  };
</script>




This will work, but if, after looking at the function, you decide to refactor and return a position object instead of x and y:



import { ref, onMounted, onUnmounted } from "vue";

export function useMousePosition() {
  const pos = {
    x: 0,
    y: 0
  };

  function update(e) {
    pos.x = e.pageX;
    pos.y = e.pageY;
  }

  // ...
}




The problem with this approach is that the client of the composition function must always have a reference to the returned object in order for reactivity to persist. This means that we cannot destructure the object or apply the spread operator:



//  
export default {
  setup() {
    //  !
    const { x, y } = useMousePosition();
    return {
      x,
      y
    };

    //  !
    return {
      ...useMousePosition()
    };

    //     
    //   `pos`  ,      x  y : `pos.x`  `pos.y`
    return {
      pos: useMousePosition()
    };
  }
};




But this does not mean that we cannot use reactive in this case. There is a toRefs () function that converts a reactive object into a simple object, each property of which is the ref of the corresponding property of the original object.



function useMousePosition() {
  const pos = reactive({
    x: 0,
    y: 0
  });

  // ...
  return toRefs(pos);
}

// x & y  ref!
const { x, y } = useMousePosition();




Thus, there are some aspects to consider when creating composition functions. If you understand how the client code works with your functions, then everything will be OK.



TOTAL



When I first started using the Composition API, I was puzzled by the question of when to apply ref () and when to reactive (). Maybe I'm still not doing it right, and until someone tells me this, I'll stick with this approach. Hopefully helped clarify some of the issues and would like to hear your feedback.



Happy coding



All Articles