How to omit `.value` in refs (Vue 3 Composition API)
Tags: vue, tutorial
Published: 2022-06-17 08:55:05


## How to omit `.value` in refs (Vue 3 Composition API)

A technical article that expounds on how we can omit using `.value` in VueJS ref creating APIs by converting them into [**reactive variables**](https://vuejs.org/guide/extras/reactivity-in-depth.html) using macros.

## Introduction

When [Vue 3](https://vuejs.org) first came around, it introduced the [Composition API](https://vuejs.org/api/composition-api-setup.html). The API allowed for greater code re-usability as well as a better way to organize Vue JS code. Along with the composition API, came the concept of `refs`. To access the value of a `ref`, you needed to append `.value` to the name of the `ref` variable. To tackle this, the Vue team came up with a solution [(Reactivity Transform)](https://github.com/vuejs/rfcs/discussions/369) that would allow us to create reactive variables without creating refs.

## Prerequisites

This article is primarily aimed at intermediate Vue 3 developers and assumes three things.

- Intermediate knowledge of JavaScript
- Basic [Vue 3](https://vuejs.org/) usage
- A basic understanding of [how to use refs](https://vuejs.org/guide/essentials/reactivity-fundamentals.html#reactive-variables-with-ref) and [reactivity in vue](https://vuejs.org/guide/essentials/reactivity-fundamentals.html)

The functionality discussed in this article is purely opt-in and existing behaviour is unnafected.

### Tools needed

For this to work, you will need to be using `vue@^3.2.25` and above. No additional dependencies are required. Vue 3.2.25+ ships an implementation under the package [@vue/reactivity-transform.](https://github.com/vuejs/vue-next/tree/master/packages/reactivity-transform) It is also integrated (with its APIs re-exported) in `@vue/compiler-sfc` so most userland projects won't need to explicitly install it.

## Reactivity in Vue 3

Reactivity refers to the ability to keep track of changes that occur in our applications. One such way to achieve reactivity in Vue 3 is by using `refs`.

### Creating Refs

The syntax for creating a ref would be something on the lines of this.

```js
import { ref } from "vue";

// By wrapping our default value (true) with a ref, we tell vue to keep track of changes made to it
const isReading = ref(true);
```

This means that when the value of `isReading` changes, Vue knows about it and it can keep track of the changes. This means that UI is automatically updated whenever the value of `isReading` changes. In your template file, you would access the reactive value the same way you would access any variable, for example:

```vue
<template>
  <h1>{{ isReading ? "Shhh, I'm reading" : "Talk to me" }}</h1>
</template>
```

### Using refs

That's all fine and dandy, but when you want to access or modify the value of the refs in the script, then you need to append a `.value` at the end of it. This is because `ref()` wraps the actual variable(`isReading`) in an object that can keep track of any changes made to it.

```js
import { ref } from "vue";

const isReading = ref(true);

// prints an object that represents the ref object that wraps isReading
console.log(isReading);

// This is how you would need to access the value of isReading
console.log(isReading.value); // prints true
```

## Reactivity Transform

### Removing the need for `.value`

The new Vue 3 syntax allows you to use refs without needing to use `.value`. To make this work, the Vue team implemented [Reactivity Transform](https://github.com/vuejs/rfcs/discussions/369#discussioncomment-2132119). This allows us to create **reactive variables** for every API that creates refs instead of using `refs`. This means we can use our variables without appending `.value` everywhere. Reactive variables **do not** need `.value` to be accessed while `refs` need you to append `.value`.

Previously we used to write code like this

```js
const isReading = ref(true);

console.log(isReading.value);
```

which can now be written like this

```js
// Prepending $ to ref makes $ref() a macro that wraps around the original ref()
const isReading = $ref(true);

console.log(isReading); // no need to write
```

Behind the scenes, Vue will unwrap the `$ref()` and compile into the original `.value` syntax we are used to writing. The only difference is that this time you don't have to write `isReading.value` everywhere. This is particularly useful in areas where the `ref` created is used in multiple places within a script.

It is also worth noting that every reactivity API that returns `refs` will have a `$`-prefixed macro equivalent.
These APIs include:

`ref` -> `$ref`
`computed` -> `$computed`
`shallowRef` -> `$shallowRef`
`customRef` -> `$customRef`
`toRef` -> `$toRef`

#### Do you need to import `$ref` ?

Since `$ref` and equivalents are [macros](<https://en.wikipedia.org/wiki/Macro_(computer_science)>), they _do not_ need to be imported. However, if you would like to import them explicitly, you can do so from `vue/macros`.

```js
import { $ref } from "vue/macros";
```

### Convert an existing ref as reactive variable using `$()`

In situations where we have a function that returns a `ref`, the Vue compiler would not be able to know that the function will return a `ref` ahead of time. In such cases, we can wrap the function call with `$()` to explicitly convert it into a reactive variable.

```js
function getIsReadingRef() {
  return ref(true);
}

const isReading = $(getIsReadingRef());
```

### Destructuring objects of refs

Previously, if you tried to destructure an object that was a `ref`, the destructured variables would lose their reactivity.

Let's go with an example ref.

```js
const getDefaultReader = () => ref({ name: "VueJS lover", timeOnPage: 30 });
```

```js
// Vue will be able to tell when any part of `reader` changes
const reader = ref(getDefaultReader());

// Vue won't be able to tell when the values of `name` and `timeOnpage` change
const { name, timeOnPage } = ref(getDefaultReader());
```

With Reactivity transform, you can destructure the objects of refs _and maintain reactivity_. You do so by wrapping the value with a `$()`.

```js
// Vue will now be able to tell when the values of `name` and `timeOnpage` change
const { name, timeOnPage } = $(getDefaultReader());
```

The above code will compile to:

```js
const __temp = getDefaultReader(),
  name = toRef(__temp, "name");
timeOnPage = toRef(__temp, "timeOnPage");
```

### Reactive props destructuring

This example is from [the original Reactivity Transform RFC.](https://github.com/vuejs/rfcs/discussions/369)

> There are two pain points with the current `defineProps()` usage in `<script setup>`

- Similar to `.value`, you need to always access `props` as `props.x` in order to retain reactivity. This means you cannot destructure defineProps because the resulting destructured variables are not reactive and will not update.
- When using the type-only `props` declaration, there is no easy way to declare default values for the `props`. We introduced the `withDefaults()` API for this exact purpose, but it's still clunky to use.

```vue
<script setup lang="ts">
interface Props {
  msg: string;
  count?: number;
  foo?: string;
}

const {
  msg,
  // default value just works
  count = 1,
  // local aliasing also just works
  // here we are aliasing `props.foo` to `bar`
  foo: bar,
} = defineProps<Props>();

watchEffect(() => {
  // will log whenever the props change
  console.log(msg, count, bar);
});
</script>
```

The above will be combined to the following in runtime

```js
export default {
  props: {
    msg: { type: String, required: true },
    count: { type: Number, default: 1 },
    foo: String,
  },
  setup(props) {
    watchEffect(() => {
      console.log(props.msg, props.count, props.foo);
    });
  },
};
```

## Using $$() to retain reactivity

To get around reactivity loss in certain scenarios, the `$$()` macro can be used.

### Retaining reactivity when passing refs as function arguments

Consider a situation where you have a function that needs to accept a reactive variable as an argument.

```ts
function trackChange(isReading: Ref<boolean>) {
  watch(isReading, (isReading) => {
    console.log("isReading changed!", isReading);
  });
}

let isReading = $ref(true);

// This will not work
trackChange(isReading);
```

In such a case, reactivity is lost.The reason for this is that the `isReading` ref is actually unwrapped into `isReading.value` when being passed in as the argument for `trackChange` while `trackChange` expects an actual `ref`. The above code compiles to this:

```ts
import { ref } from "vue";

let isReading = ref(true);

// This is what is actually happening
trackChange(isReading.value);
```

To get around this, we can wrap the ref in `$$()` which tells the compiler not to append a `.value` to it.

```ts
// This will work
trackChange($$(isReading));
```

The above example compiles to this:

```ts
import { ref } from "vue";

let isReading = ref(true);

// This is what we want - the isReading variable should be passed as a ref
trackChange(isReading);
```

### Retaining reactivity when returning inside function scope

Another scenario where reactivity is lost is when we are returning reactive variables from within a function.

```ts
function useMouse() {
  let x = $ref(0);
  let y = $ref(0);

  // listen to mousemove...

  // doesn't work!
  return {
    x,
    y,
  };
}
```

Similar to the example with passing refs as arguments, the above return statement compiles to:

```ts
return {
  x: x.value,
  y: y.value,
};
```

In order to maintain the reactivity of `x` and `y`, we can wrap the entire return statement with the `$$()` macro.

```ts
function useMouse() {
  let x = $ref(0);
  let y = $ref(0);

  // listen to mousemove...

  // This works
  return $$({
    x,
    y,
  });
}
```

### Retaining reactivity on destructured props

`$$()` works on destructured props since they are reactive variables as well. The compiler will convert it with toRef for efficiency:

```ts
const { count } = defineProps<{ count: number }>();

passAsRef($$(count));
```

compiles to:

```ts
setup(props) {
  const __props_count = toRef(props, 'count')
  passAsRef(__props_count)
}
```

## TypeScript & Tooling Integration

Vue will provide typings for these macros (available globally) and all types will work as expected. There are no incompatibilities with standard TypeScript semantics so the syntax would work with all existing tooling.

This also means the macros can work in any files where valid JS/TS are allowed - not just inside Vue SFCs.

Since the macros are available globally, their types need to be explicitly referenced (e.g. in a env.d.ts file):

```ts
/// <reference types="vue/macros-global" />
```

When explicitly importing the macros from `vue/macros`, the type will work without declaring the globals.

## Conclusion

By taking advantage of the macros added to Vue 3, you can drastically cleanup your code base by getting rid of `.value` usage. You also get to preserve reactivity within your application when destructuring reactive variables as well as props when using the Composition API and `defineProps()`.

If you'd like to read more on the same, you can do so in [the official Vue JS RFC discussion for the feature.](https://github.com/vuejs/rfcs/discussions/369)

I do hope you find this helpful in reducing your code footprint and making your general life easier. The next time you think of using `.value` for your refs, remember that you don't have to. With that, thanks for stopping by(e)!

---

_This Dot Labs is a development consultancy focused on providing staff augmentation, architectural guidance, and consulting to companies._

_We help implement and teach modern web best practices with technologies such as React, Angular, Vue, Web Components, GraphQL, Node, and more._