10 mistakes to avoid when using Vue 3

Vue 3 has been stable for quite some time. Many codebases use it in production, and everyone else will eventually have to migrate to Vue 3. I’ve now had a chance to use it and documented my mistakes, here are some you might want to avoid.

Declare primitive values ​​using Reactive

Data declaration used to be very straightforward, but now there are many helper functions at our disposal. The current rules are:

  • reactiveStatement of useObject, Array, Map, Set
  • refStatement of useString, Number, Boolean

Using it for a primitive value reactivereturns a warning, and the value does not become reactive data.

/* DOES NOT WORK AS EXPECTED */
<script setup>
import { reactive } from  "vue" ;

const count = reactive ( 0 );
</script>

Paradoxically, another way is possible. For example, use refto declare that one Arraywill be called internally reactive.

Deconstructing responsive data

Let’s say you have a reactive object with countproperties and a button to increment it count.

<template>
   Counter : {{ state. count }}
  <button @click= "add" > Increase </button>
</template>

< script > import { reactive } from "vue" ;
 export default {
   setup (
  ) {
     const state = reactive ({ count : 0 });

    function  add () {
      state.count ++ ;
    }

    return {
      state,
      add,
    };
  },
};
</script>​​

The above logic is fairly straightforward and works as expected, but you might take advantage of JavaScript’s destructuring to do the following:

/* DOES NOT WORK AS EXPECTED */
<template>
  < div > Counter: {{ count }} </ div > 
  < button @ click = "add" > Increase </ button >
</template>

< script > import { reactive } from "vue" ;
 export default {
   setup (
  ) {
     const state = reactive ({ count : 0 });

    function  add () {
      state.count ++ ;
    }

    return {
      ...state,
      add,
    };
  },
};
</script>​​

The code looks the same and should work based on our previous experience, but in fact Vue’s reactive tracking works via property access. This means that we cannot assign or destructure a reactive object because the reactive connection to the first reference has been broken. This is one of the limitations of using reactive helper functions.

Confused about .value

Likewise, using refa weird pattern can be difficult to get used to.

RefReceives a value and returns a reactive object. The value is available inside the object under .valuethe property.

const count = ref ( 0 )

console . log (count) // { value: 0 } 
console . log (count. value ) // 0

count. value ++
 console . log (count. value ) // 1

But refit will be unpacked when used in a template file and is not needed .value.

<script setup>
 import { ref } from  'vue'

const count = ref ( 0 )

function  increment () {
  count.value ++​
}
</script>

< template > 
  < button @ click = "increment" >
    {{ count }} // no .value needed
  </ button > 
</ template >

But be careful! Unwrapping only works in top-level properties. The following code snippet will be generated [object Object].

// DON'T DO THIS
<script setup>
import { ref } from  'vue'

const object = { foo : ref ( 1 ) }

</script>

<template>​​
  {{ object.foo + 1 }} // [object Object]
</template>​​

Getting it right .valuetakes time. Even though I sometimes forget how to use it, I use it more and more often.

trigger event

Since the initial release of Vue, child components have been able to emitcommunicate with their parent components using . You just need to add a custom event listener to listen for an event.

// Subcomponent 
this .$emit( ​​'my-event' )

// Parent component 
<my-component @my-event= "doSomething" />

Now, emityou need to use defineEmitsto make the declaration.

<script setup>
 const emit = defineEmits ([ 'my-event' ])
 emit ( 'my-event' )
</script>

Another thing to keep in mind is that defineEmitsneither and definePropsnor need to be imported. They are automatically available when used script setup.

<script setup>
 const props = defineProps ({
   foo : String
})

const emit = defineEmits ([ 'change' , 'delete' ])
 // setup code 
</script>

Finally, since the event must now be declared, there is no need to use .nativethe modifier, in fact it has been removed.

Declare additional options

Options API methods have several properties script setupthat are not supported in .

  • name
  • inheritAttrs
  • Customization options required by the plugin or library

The solution is to set up two different scripts in the same component as definedscript setup by the RFC .

<script>
   export  default {
     name : 'CustomName' ,
     inheritAttrs : false ,
     customOptions : {}
  }
</script>

< script  setup > // script logic setup </ script >
  

Use responsive transformations

Reactivity Transform is an experimental but controversial feature of Vue 3 that aims to simplify the way components are declared. The idea is to leverage compile-time transformations to automatically unpack one refand make it .valueobsolete. But now it is abandoned and will be removed in Vue 3.3. It’s still available as a package, but since it’s not part of Vue core, it’s best not to invest time in it.

Define asynchronous components

Previously asynchronous components were declared by wrapping them in a function.

const  asyncModal = () => import ( './Modal.vue' )

Starting from Vue 3, asynchronous components need to be defineAsyncComponentexplicitly defined using helper functions.

import { defineAsyncComponent } from  'vue'

const asyncModal = defineAsyncComponent ( () =>  import ( './Modal.vue' ))

Use redundant wrapped elements in templates

In Vue 2, component templates require a single root element, which sometimes introduces unnecessary wrapping elements.

<!-- Layout . vue -->
 < template > 
  < div > 
    < header > ... </ header > 
    < main > ... </ main > 
    < footer > ... </ footer > 
  </ div > 
</template>​​

This is no longer necessary as multiple root elements are now supported. 🥳

<!-- Layout . vue -->
 < template > 
  < header > ... </ header > 
  < main  v-bind = "$attrs" > ... </ main > 
  < footer > ... </ footer > 
</ template >

Using wrong lifecycle

All component lifecycle events have been renamed, either by adding ona prefix or changing the name entirely. You can see all the changes in the chart below.

Don’t read the document

Finally, the official documentation has been modified to reflect the new API and includes many valuable instructions, guides, and best practices. Even if you are an experienced Vue 2 engineer, you are bound to learn something new by reading the documentation.

Summarize

Every framework has a learning curve, and Vue 3’s learning curve is undoubtedly steeper than Vue 2’s. I’m still not convinced that the migration effort between the two versions is justified, but the composed API is much neater and will feel natural after you get the hang of it.

Finally, remember:

Making mistakes is much better than doing nothing.

Making mistakes is a lot better than not doing anything.

That’s all for this article. If it helps you, please like, collect, and forward~