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.
Contents
- 1 Declare primitive values using Reactive
- 2 Deconstructing responsive data
- 3 Confused about .value
- 4 trigger event
- 5 Declare additional options
- 6 Use responsive transformations
- 7 Define asynchronous components
- 8 Use redundant wrapped elements in templates
- 9 Using wrong lifecycle
- 10 Don’t read the document
- 11 Summarize
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:
reactive
Statement of useObject, Array, Map, Set
ref
Statement of useString, Number, Boolean
Using it for a primitive value reactive
returns 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 ref
to declare that one Array
will be called internally reactive
.
Deconstructing responsive data
Let’s say you have a reactive object with count
properties 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 ref
a weird pattern can be difficult to get used to.
Ref
Receives a value and returns a reactive object. The value is available inside the object under .value
the property.
const count = ref ( 0 ) console . log (count) // { value: 0 } console . log (count. value ) // 0 count. value ++ console . log (count. value ) // 1
But ref
it 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 .value
takes 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 emit
communicate 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, emit
you need to use defineEmits
to make the declaration.
<script setup> const emit = defineEmits ([ 'my-event' ]) emit ( 'my-event' ) </script>
Another thing to keep in mind is that defineEmits
neither and defineProps
nor 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 .native
the modifier, in fact it has been removed.
Declare additional options
Options API methods have several properties script setup
that 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 ref
and make it .value
obsolete. 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 defineAsyncComponent
explicitly 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 on
a 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~