preface

It has been more than half a year since Vue 3.0 was released. It took me a long time to change my thinking from the original Option API to Composition API, and there were many problems in the process of using it. We all know that refs and Reactive are ways of defining responsive data, but I had only one conclusion from most blogs on the Internet when I was learning it: Ref defines the basic type data, and Reactive defines the reference type data. However, after practice, I find that it is not very rigorous, so I found this article, which I think is very good, and I have the translation today. The following original translation is not literal translation, if there is any mistake, please criticize and correct.

The original translation

As of this writing, the release of Vue 3 is getting closer and closer. I think I’m most excited to see how other developers embrace it and use it. Although I’ve had the opportunity to use Vue 3 over the past few months, I know that’s not true of everyone.

The biggest feature of Vue 3 is the Composition API. This provides an alternative way of creating components that is radically different from the existing Option API. I don’t hesitate to admit that I didn’t understand it when I first saw it, but as I used it more, I found it started to make sense. While you won’t use the Composition API to rewrite your entire application, it will get you thinking about how you can further improve the way you create components and write functionality. I’ve been giving a couple of talks on Vue 3 recently, and one question that keeps coming up is when to use Ref vs Reactive to declare the responsiveness of data. I don’t have a good answer, so over the past few weeks, I’ve set out to answer that question, and this paper is the result of that research.

I would also like to point out that this is my own view and please do not regard it as the “way” to be taken. Until someone tells me a better way to use Ref & Reactive, I’m going to use it the following way for now. With any new technology, I think it takes some time to figure out how we use it to come up with some best practices. Before I begin, I’ll assume that you have at least an understanding of the Composition API. This article will focus on Ref vs Reactive rather than the mechanics of the Composition API, so let me know if you’re interested in an in-depth tutorial on this.

Response in Vue 2

To provide some background for this article, I want to quickly explore how to create responsive data in Vue 2 applications. When you want Vue to track data changes, you need to declare this property inside the object returned from the data function.

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

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

Inside Vue 2, to track changes to each data, it looks at each property and uses Object.defineProperty() to create getters and setters. This is the most basic interpretation of Vue 2 responsive data, but I know it’s not “magic”. You can’t just create data anywhere and expect Vue to keep track of it; you have to follow the conventions that define it in the data() function.

Ref vs Reactive

With the Options API, you have to follow a few rules when defining responsive data, and the Composition API is no exception. You can’t just declare data and expect Vue to track changes. In the example below, I define a title property and return it from the setup() function to use in the template.

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

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

While it works, the title property is not responsive data. This means that if some method changes the title property, the DOM does not update the data. For example, if you want to update the title after 5 seconds, the following action will not work.

<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>
Copy the code

To solve the above example, we can use import {ref} from ‘vue’ and mark it as responsive data using ref(). Inside Vue 3, Vue creates a Proxy Proxy object.

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

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

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

      setTimeout(() = > {
        // you might be asking yourself, what is this .value all about...
        // more about that soon
        title.value = "New Title";
      }, 5000);

      return{ title }; }};</script>
Copy the code

I also want to be clear that when it comes to Ref vs Reactive, I believe there are two scenarios: the first is when you create components like we did above, where you need to define Reactive data, and the second is when you create composite functions that can be reused. In this article, I’ll explain each case.

Ref

If you want to make raw data types reactive, ref() is your first choice. Again, it’s not a silver bullet, but it’s a good starting point. If you need a refresher, the seven primitive data types in JavaScript are:

  • 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); }};Copy the code

In the previous example, we had a string named title, so ref() is a good choice for declaring reactive data. If you have questions about the code we wrote below, don’t worry, I have the same problem.

import { ref } from "vue";

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

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

    return{ title }; }};Copy the code

Why use a const declaration when the original value is about to change? Shouldn’t we use let here? If you’re using console.log(title), you might want to see the values Hello, Vue 3! Instead, get an object that looks like this:

{_isRef: true}
value: (...)._isRef: trueGet value: ƒ value() Set value: ƒ value(newVal)__proto__: Object
Copy the code

The ref() function takes an internal value and returns a reactive and mutable ref object. The ref object has a single attribute.value that points to an internal value. This means that if you want to access or change a value, you need to use title.value. And because this is an object that doesn’t change, I decided to declare it const.

Ref split open a case

The next question you might ask is “Why don’t we have to reference.value in the template?”

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

Copy the code

When ref is returned as an attribute of the render context (the object returned from Setup ()) and accessed in the template, it is automatically expanded to an internal value without the need to append a. Value to the template, a process also known as “unboxing”.

Evaluate properties work the same way, so if you want to use the value of the evaluated property in the setup() method, use.value.

Reactive

When you define reactive data on raw values, we just looked at some examples using ref(). What happens if you create reactive objects (reference types)? In this case, you can still use ref(), but internally you just call the reactive() function, so I’ll stick with reactive().

Reactive (), on the other hand, will not work with raw values, and reactive() retrieves an object and returns the original object as a reactive proxy. This is equivalent to the 2.x vue.Observable () and has been renamed to avoid confusion with RxJS Observables.

import { reactive } from "vue";

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

    return{ data }; }};Copy the code

The biggest difference here is when you want to access data defined by Reactive () in the template. You’ll need to reference data.title in the template, and in the previous example, data is an object that contains a property called title.

Ref vs Reactive in Components

Based on everything we’ve discussed so far, the answer is simple, right? We should only use ref() for primitive type data and reactive() for reference type data. This is not always the case when I start building components, and in fact the documentation states:

The difference between using ref and reactive can be somewhat compared to how you would write standard JavaScript Ref and Reactive are a little different than how you write normalized JS logic.

I started thinking about this and came to the following conclusion.

In the example, we see a single property called title, which is a String, and it makes sense to use ref(). But as my application began to get complicated, I defined 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 }; }};Copy the code

In this case, I would put them all into one object and use the reactive() method.

<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>
Copy the code

This is how I have always used Ref vs Reactive in components, but I would like to hear from you. Are you doing something similar? Is this the wrong approach? Please give me some feedback below.

Create composite logic (reusable)

Using ref() or Reactive () in a component creates reactive data, and as long as you know how to access that data in setup() methods and templates, you shouldn’t have any problems.

When you start writing composable functions, you need to understand the differences between them. I’ll use the example in the RFC documentation because it does a good job of explaining side effects.

One requirement, for example, is to create logic to track the user’s mouse position, and to have the ability to reuse this logic in any component that needs it. You have now created a composite function that tracks x and y coordinates and returns them to the consumer.

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 };
}
Copy the code

If you want to use this logic in a component, you can call the composite function, deconstruct the returned object, and then return the X and y coordinates to the template for use.

<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>
Copy the code

The above code works fine, but if you want to refactor x and y into a position object:

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;
  }

  // ...
}
Copy the code

The problem with this approach is that the caller of a composite function must always keep a reference to the returned object in order to remain responsive. This means that the object cannot be deconstructed or expanded:

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

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

    // this is the only way to retain reactivity.
    // you must return `pos` as-is and reference x and y as `pos.x` and `pos.y`
    // in the template.
    return {
      pos: useMousePosition() }; }};Copy the code

This doesn’t mean you can’t use reactive style. There is a toRefs() method that converts a reactive object to a normal object, with the result that each property on the object is a reactive reference to the original object.

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

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

// x & y are now refs!
const { x, y } = useMousePosition();
Copy the code

conclusion

When I first started using the Composition API to create components, I had a hard time understanding when I needed ref() and reactive(). There may be some mistakes in the case studied above, but I hope someone can tell me some better ways. I hope I can help you out with some of your problems and look forward to hearing your feedback below. Thank you for reading, my friend as always…

The translator to summarize

  • useComposition APINeed to be insetupFunction, and returns the data needed for the templatescript setup)
  • The way Vue 2 creates internal responsive data is indata()Function is defined in the object returned by theObject.defineProperty()Set for each propertygetterandsetterTo track data changes. Vue 3 is used internallyProxyProxy object to implement data responsiveness.
  • ref()The defined reactive data needs to pass through.valueIn the templateSplit open a caseThe operation does not need to be manually passed.valueTo access.reactive()The object returned by the function needs to pass through the template.Operator access.
  • ref()Reactive data can be created for primitive and reference type values, but is internally called when reactive data is created for reference typesreactive(). whilereactive()Only one object can be received. We can put some associated data into this object to improve the readability of the code.
  • You can use composite functions if the logic can be reused so that other components can use the logic.reactive()If the object returned by the function is deconstructed, the data inside will be unresponsive and can be passedtoRefsConvert each property in the object torefTo use.

The original link

Vue 3 Composition API: Ref vs Reactive