Vue
Vue 3
Refs
- Composition API - An animated explanation
- Vue2 to Vue3 — What’s changed?
- Why vite ?
- A definitive guide to Vue 3 components
- ViteJS
- What is the difference between “vite” and “vite preview”?
Vue 3 VS Vue 2
Composition API vs Options API
Options API is function concerned.
Composition API is logic concerned.
For complex components, code of same logic may be scattered in props
, data
, methods
, mounted
, which makes it hard to maintain. So in Vue 3, Composition API is introduced, code of same logic is put together in setup
.
See which to choose.
createApp vs vue instance
In Vue 2, we mount a App.vue
instance to #app
:
1 | new Vue({ |
In Vue 3, we createApp
from a App.vue
and mount to #app
:
1 | createApp(App).use(store).use(router).mount('#app') |
Why use createApp
over new Vue
?
In Vue 2, any directives created using Vue
object will be usable by all application instances. This becomes a problem when our web app has multiple Vue application instances but we want to limit certain functionality to specific instances.
1 | // The only way to create a directive in Vue 2 |
Vue 3 solves this problem by creating directives on app
instance instead of Vue
object:
1 | // Both of the application instances can access the directive |
Vite
Vue 3’s scaffolding tool migrated from vue-cli
to ‘create-vue’, which is based on vite
and has a much faster building speed than webpack
. See why vite is much faster
For more information about vite, check out my post named Vite
on this blog.
Vue 3 features
Composition API
- Difference with
Options API
already explained above. - Use
<script setup>
or<script>setup()
to indicate Composition API. Difference between<script setup>
and<script>setup()
is that noreturn
is required in<script setup>
to pass objects to template. - A typical
<script setup>
SFC(Single File Component) goes here:
FAQ
ref() vs reactive()
reference:
ref vs reactive in Vue 3
Reactivity CoreUsage
ref: Returns a deep reactive mutable object, point to inner value with .value. If an object assigned, reactive() is called. UseshallowRef()
to avoid deep conversion.
reactive: Returns a deep reactive proxy of the object.Example
ref:
1 | const count = ref(0) |
reactive:
1 | const obj = reactive({ count: 0 }) |
Key Points
reactive()
only takes objects, NOT JS primitivesref()
is callingreactive()
behind the scenes, objects work for both- BUT,
ref()
has a.value
property for reassigning,reactive()
does not have this and therefore CANNOT be reassigned
Use
ref()
when..
- it’s a primitive
- it’s an object you need to later reassign (like an array - more info here)
reactive()
when..
- it’s an object you don’t need to reassign, and you want to avoid the overhead of
ref()
In Summary
ref()
seems like the way to go since it supports all object types and allows reassigning with .value
. ref()
is a good place to start, but as you get used to the API, know that reactive()
has less overhead, and you may find it better meets your needs.
ref()
Use-Case
You’ll always use ref()
for primitives, but ref()
is good for objects that need to be reassigned, like an array.
1 | setup() { |
The above with reactive()
would require reassigning a property instead of the whole object.
1 | setup() { |
reactive()
Use-Case
A good use-case for reactive()
is a group of primitives that belong together:
1 | const person = reactive({ |
the code above feels more logical than
1 | const name = ref('Albert'); |
Useful Links
If you’re still lost, this simple guide helped me: https://www.danvega.dev/blog/2020/02/12/vue3-ref-vs-reactive/
An argument for only ever using ref()
: https://dev.to/ycmjason/thought-on-vue-3-composition-api-reactive-considered-harmful-j8c
The decision-making behind why reactive()
and ref()
exist as they do and other great information, the Vue Composition API RFC: https://vuejs.org/guide/extras/composition-api-faq.html#why-composition-api
ref unwrap
reference:
Reactivity Core
Vue Cli Plugins and Presets
reference: Plugins and Presets
- What are plugins ?
Plugins modify the internal webpack configuration and inject commands to vue-cli-service.
Most of the features listed during the project creation process are implemented as plugins.
If you inspect a newly created project’s package.json, you will find dependencies that start with @vue/cli-plugin-
. These are plugins.
Add a plugin to an existing project
Usevue add [plugin name]
to add a plugin to an existing project.
For example, usevue add eslint
to addeslint
linter to the project.What is a Vue Cli preset ?
A JSON object that contains pre-defined options and plugins for creating a new project.
Example:
1 | { |
How to get query string in vue 3 ?
- In vue 2, we got
this.$route.query
to get query string. - In vue 3, first
import { useRoute } from 'vue-router'
, thenuseRoute().query
to get query string.
Useful commands
- Use
vue ui
to start a ui interface inside a project created byvue-cli
. - Use
vue add typescript
to add typescript plugin to a vue-cli project and transform it to a typescript project.
Vue Component Communication
Component communication has three forms:
- Parent -> Child
- Child -> Parent
- Global
- Parent send messages to child through props. Any change to prop is reflected immediately. Prop is immutable in child so vice versa not viable. This is a one-way communication.
- Child
this.$emit('event-name', p1, p2)
, parent receive the event through<child @event-name='handler' />
- Global communication can be done in two ways:
- A:
this.$root.$emit('event-name', p1, p2)
B:this.$root.$on('event-name', (p1, p2) => {})
- Use vue-events. A:
this.$events.$emit('event', p1, p2)
B:this.$events.$on('event', (p1, p2) => {})
- A:
Pitfalls
- Using
this.$root.$emit/$on
, be careful not to call$on
multiple times with the same handler, this will cause the handler to be bound multiple times on the same event. Use$off
to unbind at appropriate time.- Example: A component binds a handler to an event in the
mount
hook. When the component is destroyed, the handler is still bound to that event. If the component is created repeatedly, this will also cause the handler to be bound repeatedly on the same event. - This also applies to
vue-events
- Example: A component binds a handler to an event in the
Vue 2
Watch
- Trigger a function whenever a reactive property it depends changes:
1 | new Vue({ |
- immediate property:
- Eager watchers
- Will be called on data initialization
1
2
3
4
5
6
7
8
9
10
11
12
13new Vue({
data: {
count: 0
},
watch: {
count: {
handler(newValue, oldValue) {
console.log(`count changed from ${oldValue} to ${newValue}`)
},
immediate: true
}
}
}) - The output will be
count changed from undefined to 0
.
Vue reactivity
Object
- Existing object properties are reactive
- New properties added on the fly:
this.obj.newProp = 10
will not add a reactivenewProp
toobj
Vue.set(this.obj, 'newProp', 10)
orthis.$set(this.obj, 'newProp', 10)
will add a reactivenewProp
tothis.obj
- Once the new property is added using
Vue.set
orthis.$set
,subsequent assignment operations no long need to useVue.set
orthis.$set
, just use normal assignment operation likethis.obj.newProp = 11
.
1 | <template> |
Array
Array change using methods like
push/pop
will be detected. Changes on array items will not be detected.
Vue.nextTick
Vue.nextTick is a method provided by the Vue.js framework that allows you to schedule a function to be executed after the next DOM update cycle.
In Vue.js, when you modify the data in a component, Vue updates the virtual DOM and then applies those changes to the real DOM. However, this process is asynchronous, so if you want to perform some action immediately after the DOM has been updated, you can’t rely on the DOM being up-to-date right away.
This is where Vue.nextTick comes in. It allows you to schedule a function to be executed after the next DOM update cycle, which means that the DOM will be fully up-to-date when the function is executed.
1 | Vue.component('my-component', { |
- In this example, when the component is mounted, the message data property is changed to “Hello World!”. Then, Vue.nextTick is called with a callback function that logs the text content of the component’s root element. Since Vue.nextTick schedules the callback to be executed after the next DOM update cycle, the text content will be “Hello World!”, which is the updated value of the message property.
Vue Plugin
- Plugin use case: global methods/properties/assets(directives/filters/transitions)/mixin/Vue instance/library
Vue 2
1 | // main.js |
1 | // MyPlugin.js |
Vue 3
1 | <h1>{{ $translate('greetings.hello') }}</h1> |
1 | // main.js |
1 | // plugins/i18n.js |
Vuex
- A global state manage with reactive props.
- Note:
- Do not change props directly. Commit mutations otherwise changes will not be seen.
Vuex workflow
State
- Map
state
incomputed
.
1 | // store.js |
1 | <!-- component.vue --> |
Getters
getters
is a computed property for global state.getters
are called without brackets.getters
are also mapped incomputed
.
1 | // store.js |
1 | <!-- component.vue --> |
Mutations
- Mutation is the only way to change state.
- Mutation must be synchronous.
If we have a mutation like this:
1 | mutations: { |
Now imagine we are debugging the app and looking at the devtool’s mutation logs. For every mutation logged, the devtool will need to capture a “before” and “after” snapshots of the state. However, the asynchronous callback inside the example mutation above makes that impossible: the callback is not called yet when the mutation is committed, and there’s no way for the devtool to know when the callback will actually be called - any state mutation performed in the callback is essentially un-trackable!
- Mutations are mapped in methods.
1 | import { mapMutations } from 'vuex' |
Actions
actions
do not mutate state directly, they only commitmutations
.actions
can be asynchronous.actions
are mapped in methods.
1 | // store.js |
1 | <!-- component --> |
Modules
1 | // typical mutli module |
- Access root state in module:
1 | import Vue from 'vue' |
- By default, actions, mutations and getters are all registered under global scope. Calling them may call all the same named correspondent in modules. Be careful not to have same name.
- Namespacing is introduced to avoid naming conflicts.
1 | import Vue from 'vue' |
Two-way binding
1 | <script> |
Others
Add global property to Vue instance
1 | // Vue 2 |
Global css
In main.js
file: import './assets/css/main.css'
Scoped css
By default, styles wrapped by <style>
tags are global.
When a <style>
tag has the scoped
attribute, its CSS will apply to elements of the current component only.
It works by adding a unique data-v
attribute to the component:
1 | <style scoped> |
1 | <style> |
created
vs mounted
When created
, DOM has not yet been mounted
. No DOM operation can be done.
Created is generally used for fetching data from backend API and setting it to data properties. But in SSR mounted() hook is not present you need to perform tasks like fetching data in created hook only.
Two-way binding
Make child data sync parent’s data
Method 1: Use
update
1 | <!-- child --> |
1 | <!-- parent --> |
- Method 2: Use
sync
1 | <!-- child --> |
1 | <!-- parent --> |
v-model modifiers
.number
:- If you want user input to be automatically typecast as a Number, you can add the number modifier to your v-model managed inputs:
<input v-model.number="age" type="number">
- This is often useful, because even with type=”number”, the value of HTML input elements always returns a string. If the value cannot be parsed with parseFloat(), then the original value is returned.
Watch nested data
watch: { item: { handler(val){ // do stuff }, deep: true } }