Vue-next (Vue 3) has been out for a while. It is now under release candidate stage which means there won’t be big changes on the open APIs. Good to see that Vue has already been stabilized and ready to waltz into our projects.
I have to say that Vue 2 is already amazing enough. But with Vue 3’s new features, it’s likely to upgrade our projects to an upper level. I guess the most thrilling feature in Vue 3 would be the composition APIs. Evan You himself mentioned that the composition APIs are inspired by the React hooks. Even though the two APIs hooks and compositions are a lot alike, but from the code base they are completely different. Let’s not discuss which is better or promising because I don’t really think either framework outraces another.
In all, it’s so happy to see that Vue can also do what React does. Let’s have a close look at the new features.
TLDR;
# 1. Vite
This is another work of art by Evan You which is aim at replacing Webpack in Vue development (Currently only works for Vue). It is designed to be fast just as its French name implies.
# Getting started with Vite
The official repo offers us a simple way to create a Vue 3 app via Vite.
# Npm
$ npm init vite-app <project-name>
$ cd <project-name>
$ npm install
$ npm run dev
# Yarn
$ yarn create vite-app <project-name>
$ cd <project-name>
$ yarn
$ yarn dev
# Start Dev Server
It all happened in a blink of eyes.
❯ yarn dev
yarn run v1.22.4
$ vite
vite v1.0.0-rc.4
Dev server running at:
> Local: http://localhost:3000/
> Network: http://192.168.3.2:3000/
> Network: http://10.80.67.216:3000/
Open http://localhost:3000/
# vue-next-features
I created a small app to demo the new features of Vue 3. If you take a look at the projects’ package.json
, the simplicity of vue-next-features dependencies will make you fond of Vite immediately. (I mean, who doesn’t want a simpler package.json
to start with?)
There is another Vue 3 “Hello World” repo (vue-next-webpack-preview) bundled with Webpack. It is also a good playground.
{
...,
"dependencies": {
"vite": "^1.0.0-rc.4",
"vue": "^3.0.0-rc.5"
},
"devDependencies": {
"@vue/compiler-sfc": "^3.0.0-rc.5"
}
}
{
...,
"dependencies": {
"vue": "^3.0.0-beta.2"
},
"devDependencies": {
"@vue/compiler-sfc": "^3.0.0-beta.2",
"css-loader": "^3.4.2",
"file-loader": "^6.0.0",
"mini-css-extract-plugin": "^0.9.0",
"url-loader": "^4.0.0",
"vue-loader": "^16.0.0-alpha.3",
"webpack": "^4.42.1",
"webpack-cli": "^3.3.11",¥
"webpack-dev-server": "^3.10.3"
}
}
# 2. Composition API
As the biggest the change of Vue.js, the composition API would become your next most frequently and commonly used feature. Just like React hooks, with the Vue composition API will help gain more customizibilities.
Here is a list of the Vue 3 composition APIs. (There are actually more…)
-
Reactivity
computed
reactive
ref
readonly
watch
watchEffect
unref
toRefs
isRef
isProxy
isReactive
isReadonly
customRef
markRaw
shallowReactive
shallowReadonly
shallowRef
toRaw
-
Lifecycle Hooks
onBeforeMount
onBeforeUnmount
onBeforeUpdate
onMounted
onUpdated
onErrorCaptured
onRenderTracked
onRenderTriggered
onUnmounted
onActivated
onDeactivated
Visit Vue 3 official doc to know more about these APIs. https://v3.vuejs.org/api/composition-api.html
# Component Styles
# In Vue 2
Use configuration template to define the component contents. In Vue 3, this legacy usage is still available. If you’d prefer this style, you can continue using it.
<template>
<button @click="count++">count: {{ count }}</button>
</template>
<script>
const multiplier = 2
export default {
data () {
return {
count: 0
}
},
computed: {
result () {
return this.count * multiplier
}
},
mounted () {
console.log(this.count)
},
watch: {
count (val, oldVal) {
console.log(val, oldVal)
}
}
}
</script>
# In Vue 3
To use the composition API, you’ll need to add a setup
property in to the default export. The below code is completely equivalent to the code above.
<template>
<button @click="count++">count: {{ count }}</button>
</template>
<script>
import { computed, reactive, toRefs, onMounted, watch } from 'vue'
export default {
setup () {
const multiplier = 2
const state = reactive({
count: 0
})
const result = computed(() => {
return state.count * multiplier
})
onMounted(() => {
console.log(state.count)
})
watch(state.count, (val, oldVal) => {
console.log(val, oldVal)
})
return {
...toRefs(state)
}
}
}
</script>
# Go Ahead with the new API
There are 4 reasons why you should use composition API over the default Vue 2 config template:
- To increase readability of source code
- To avoid duplicated or redundant logics
- To group up similar logics
- To reuse the logics
Compared to the Vue 2 configuration style, the logics are precisely broken down into smaller particals so that you could group the similar logics together easily. In this way, it also reduces chances jumping around from irrelavant logics. This will help increase the productivity without a doubt.
# 2. Advanced Reactivity API
Personally, I think this is nothing different to the other reactivity APIs. But it indeed offers those abilities of handling edge cases like custom hooks and shallow layer modification. It is now part of the basic reactivity API according to the Vue 3 official doc.
In the Vue composition api doc (Yes, there’s a doc only for the composition APIs), the following APIs are listed as advanced reactivity apis.
- customRef: custom hook
- markRaw: not able to be a
reactive
- shallowReactive: Object’s first layer
reactive
- shallowReadonly: Object’s first layer
readonly
- shallowRef: Object’s value not
reactive
- toRaw: restore a
reactive
to normal Object
Are you farmiliar with Debounce
? Here is an official demo of customRef
:
import { customRef } from 'vue'
const useDebouncedRef = (value, delay = 200) => {
let timeout
return customRef((track, trigger) => {
return {
get() {
track()
return value
},
set(newValue) {
clearTimeout(timeout)
timeout = setTimeout(() => {
value = newValue
trigger()
}, delay)
},
}
})
}
export default {
setup () {
return {
text: useDebouncedRef('some text')
}
}
}
# 3. v-enter-from / v-leave-from
In Vue 2, the <Transition>
component helps handle the component animation
/ transition
. But the component property v-enter-active
v-enter
v-enter-to
were quite ambiguous to me. Sometimes I’m confused which happens first.
Now in Vue 3, those transition property names became more unified and intuitive.
v-enter
=>v-enter-from
v-leave
=>v-leave-from
<template>
<transition name="fade">
<div v-show="show">fade transition</div>
</transition>
</template>
<script>
import { reactive, toRefs } from 'vue'
export default {
setup () {
const state = reactive({
show: true
})
setTimeout(() => {
state.show = false
}, 1000)
setTimeout(() => {
state.show = true
}, 2000)
return {
...toRefs(state)
}
}
}
</script>
<style>
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
.fade-enter-to,
.fade-leave-from {
opacity: 1;
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 2000ms;
}
</style>
The transition order:
v-enter-from
(v-enter)v-enter-active
v-enter-to
v-leave-from
(v-leave)v-leave-active
v-leave-to
I believe this is much easier to understand, isn’t it?
# 4. Allow Multiple Root Element
Vue 2 throws errors on multiple root element. All elements must be nested within one root element in the template.
<!-- Error -->
<template>
<div>pitayan</div>
<div>blog</div>
</template>
<!-- One Root Element only -->
<template>
<div>
<div>pitayan</div>
<div>blog</div>
</div>
</template>
Vue 3 removed this annoying usage. I think this is extremely helpful when you really don’t want to nest your elements within a “container” parent. Sometimes all you need is maybe just to insert those bare elements into the right place.
This works similarly to the React Fragments which helps mitigate the nesting issues.
<!-- Vue 3 Multiple Root Element -->
<!-- Okay -->
<template>
<div>pitayan</div>
<div>blog</div>
</template>
# 5. “Filters” is Deprecated(Removed)
I think a lot of people think that filters
is maybe an awesome feature of Vue.js. It indeed works well in Vue’s template engine. (For example, data formatting / calculation etc).
Let’s see how Vue 3 doc explains why filters
is removed:
While this seems like a convenience, it requires a custom syntax that breaks the assumption of expressions inside of curly braces being “just JavaScript,” which has both learning and implementation costs.
I believe it’s nothing bad for development without the filters
, even though it may cost you extra time on migrating to Vue 3. In my projects, the appearance of filters
is pretty a rare case since I could replace such functionality with a method
or computed
easily. Because in my opinion, method
/ computed
has higher readability than the filters
.
<template>
<!-- Deprecated (removed) & Error -->
<span>{{ count | double }}</span>
<!-- If you have to use fiter, make it a function -->
<span>{{ double(count) }}</span>
</template>
<script>
import { ref } from 'vue'
export default {
// Not working
filters: {
double (val) {
return val * 2
}
},
setup () {
const count = ref(1)
return {
count,
double: val => val * 2
}
}
}
</script>
# 6. New Async Component: Suspense
This is perhaps the only new feature of Vue 3 that may be changed even after official release. The inspiration is also from React Suspense. So the usage scenario would be the same in my opinion.
Do you remember how you render the asynchronous data previously in Vue 2? I think v-if
/ v-else
should be the answer.
<template>
<div>
<div v-for="i in items" :key="i">{{ i }}</div>
<div v-else>loading...<div>
</div>
</template>
<script>
export default {
data () {
return {
items: null
}
},
mounted () {
this.items = await new Promise(resolve => {
setTimeout(() => {
return resolve(['one', 'two'])
}, 3000)
})
}
}
</script>
With Suspense
component, you can do it without handling conditions yourself. By setting up the default
and fallback
slot, the Suspense
component will handle the async event automatically.
<template>
<suspense>
<template #default>
<div v-for="i in items" :key="i">{{ i }}</div>
</template>
<template #fallback>
Loading...
</template>
</suspense>
</template>
<script>
export default {
async setup () {
const items = await new Promise(resolve => {
setTimeout(() => {
return resolve(['one', 'two'])
}, 3000)
})
return {
items
}
}
}
</script>
# 7. Display it elsewhere: Teleport
It is another cool stuff based on React Portals. It provides the ability to insert the component to a target DOM Node.
What we do in Vue 2 to insert a custom component in <body>
(Of course there is a Vue 3rd party plugin PortalVue providing such functionality):
import Vue from 'vue'
const Ctor = Vue.extends({
template: `<div>hello world</div>`
})
const vm = new Ctor({ ... }).$mount()
document.body.appendChild(vm.$el)
To use such feature in Vue 3, wrap your target component within <Teleport>
and define the destination Node (querySelector) in to
property.
<template>
<Teleport to="body">
<div>Pitayan</div>
</Teleport>
</template>
# 8. Allow Multiple v-model
v-model
is used for data two-way bindings in form elements or even custom components. In Vue 2, a custom component can only have one v-model
in the tag.
<template>
<my-input-form v-model="input" />
</template>
Vue 3 removed the limitation and allows you to have multiple v-model
so that you could specify the bindings separately for more input elements.
<template>
<my-input-form
v-model:first="inputFirst"
v-model:second="inputSecond"
/>
</template>
# 9. Global APIs
Vue 3 offers some new APIs to help us control the components and instances better.
# createApp
In Vue 2, Vue
can be used as constructor to return an instance Object. In Vue 3, you could use createApp
function instead. The behavior is actually the same.
// Vue 2
import Vue from 'vue'
import App from '@/src/App'
new Vue({
el: '#app',
components: {
App
}
})
// Vue 3
import { createApp } from 'vue'
import App from '@/src/App'
const app = createApp(App)
What about those global methods like extend
component
 mixin
and directive
?
Same, but you need to use the instance method instead.
// Global methods
app.extend()
app.component()
app.mixin()
app.directive()
# nextTick
I think nextTick
is a frequently used API since a lot of the logics are actually asynchronous and they need to be arraged to the next DOM update cycle.
In Vue 2, nextTick
is an instance method.
export default {
...,
mounted () {
this.$nextTick(() => {
console.log('pitayan')
})
}
}
Vue 3 allows to you use nextTick
as an independent function.
// nextTick function type
export declare function nextTick(fn?: () => void): Promise<void>;
// An official doc Example
import { nextTick } from 'vue'
export default {
setup () {
const message = ref('Hello, Pitayan!')
const changeMessage = async newMessage => {
message.value = newMessage
await nextTick()
console.log('Now DOM is updated')
}
}
}
# Other Helper Functions
These new APIs will be extremely helpful when you need extra controls for much more abstracted scenarios. I personally think that they can be frequently used in the 3rd party libraries.
- h: return virtual node
- createRenderer: custom renderer that can be used for cross-environment purposes
- defineComponent: type the Object passed in
- defineAsyncComponent: load async component when necessary
- resolveComponent: resolve a component within the current instance scope
- resolveDynamicComponent: resolve a dynamic component within the current instance scope
- resolveDirective: get a
directive
from the current instance scope - withDirectives: applies
directive
to aVNode
# Conclusions
I’m very happy and honored to vitness the growth of Vue.js 2.x => 3.x. Vue team concludes what was not possible natively in Vue 2 and made them possible in Vue 3. As I could see that there are many familar stuffs from Vue 3’s code base.
It’s not hard to tell that Vue 3 is a much more solid framework. It provides a new and simpler way to organize your source code, meanwhile smaller and faster. And under the help of Typescript
and their new features for instance composition API, projects’ structure can become very much different than before. Which I believe is a positive impact to the front end community.
That’s all for the Vue 3’s new feature.
If you think this article is great. Please share it to the social networks. Thanks for reading.