Vue3-Doc-Basic-In-English
The way to learn something well is to read the official documentation
# Essentials
# Composition API | Options API Which to Choose?
Both API styles are fully capable of covering common use cases. They are different interfaces powered by the exact same underlying system . In fact, the Options API is implemented on top of the Composition API! The fundamental concepts and knowledge about Vue are shared across the two styles.
The Options API is centered around the concept of a "component instance" (this as seen in the example), which typically aligns better with a class-based mental model for users coming from OOP language backgrounds. It is also more beginner-friendly by abstracting away the reactivity details and enforcing code organization via option groups.
The Composition API is centered around declaring reactive state variables directly in a function scope, and composing state from multiple functions together to handle complexity. It is more free-form, and requires understanding of how reactivity works in Vue to be used effectively. In return, its flexibility enables more powerful patterns for organizing and reusing logic.
# App Configurations
The application instance exposes a .config object that allows us to configure a few app-level options , for example defining an app-level error handler that captures errors from all descendent components:
app.config.errorHandler = (err) => {
/* handle error */
}
2
3
The application instance also provides a few methods for registering app-scoped assets. For example, registering a component:
app.component('TodoDeleteButton', TodoDeleteButton)
This makes the TodoDeleteButton available for use anywhere in our app. We will discuss registration for components and other types of assets in later sections of the guide. You can also browse the full list of application instance APIs in its API reference.
Make sure to apply all app configurations before mounting the app!
# Restricted Globals Access
Template expressions are sandboxed and only have access to a restricted list of globals. The list exposes commonly used built-in globals such as Math and Date.
Globals not explicitly included in the list, for example user-attached properties on window, will not be accessible in template expressions. You can, however, explicitly define additional globals for all Vue expressions by adding them to app.config.globalProperties.
# DOM Update Timing
When you mutate reactive state, the DOM is updated automatically. However, it should be noted that the DOM updates are not applied synchronously. Instead, Vue buffers them until the "next tick" in the update cycle to ensure that each component needs to update only once no matter how many state changes you have made .
To wait for the DOM update to complete after a state change, you can use the nextTick() global API:
import { nextTick } from 'vue'
function increment() {
state.count++
nextTick(() => {
// access updated DOM
})
}
2
3
4
5
6
7
8
# Reactive Proxy vs. Original
It is important to note that the returned value from reactive() is a Proxy of the original object, which is not equal to the original object:
const raw = {}
const proxy = reactive(raw)
// proxy is NOT equal to the original.
console.log(proxy === raw) // false
2
3
4
5
# Reactive Proxy vs. Original
It is important to note that the returned value from reactive() is a Proxy of the original object, which is not equal to the original object:
const raw = {}
const proxy = reactive(raw)
// proxy is NOT equal to the original.
console.log(proxy === raw) // false
2
3
4
5
Only the proxy is reactive - mutating the original object will not trigger updates. Therefore, the best practice when working with Vue's reactivity system is to exclusively use the proxied versions of your state.
To ensure consistent access to the proxy, calling reactive() on the same object always returns the same proxy, and calling reactive() on an existing proxy also returns that same proxy:
// calling reactive() on the same object returns the same proxy
console.log(reactive(raw) === proxy) // true
// calling reactive() on a proxy returns itself
console.log(reactive(proxy) === proxy) // true
2
3
4
5
This rule applies to nested objects as well. Due to deep reactivity, nested objects inside a reactive object are also proxies:
const proxy = reactive({})
const raw = {}
proxy.nested = raw
console.log(proxy.nested === raw) // false
2
3
4
5
6
# Limitations of reactive()
The reactive() API has two limitations:
It only works for object types (objects, arrays, and collection types such as Map and Set). It cannot hold primitive types such as string, number or boolean.
Since Vue's reactivity tracking works over property access, we must always keep the same reference to the reactive object. This means we can't easily "replace" a reactive object because the reactivity connection to the first reference is lost:
let state = reactive({ count: 0 })
// the above reference ({ count: 0 }) is no longer being tracked (reactivity connection is lost!)
state = reactive({ count: 1 })
2
3
4
It also means that when we assign or destructure a reactive object's property into local variables, or when we pass that property into a function, we will lose the reactivity connection:
const state = reactive({ count: 0 })
// n is a local variable that is disconnected
// from state.count.
let n = state.count
// does not affect original state
n++
// count is also disconnected from state.count.
let { count } = state
// does not affect original state
count++
// the function receives a plain number and
// won't be able to track changes to state.count
callSomeFunction(state.count)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Reactive Variables with ref()
To address the limitations of reactive(), Vue also provides a ref() function which allows us to create reactive "refs" that can hold any value type:
import { ref } from 'vue'
const count = ref(0)
2
3
const count = ref(0)
console.log(count) // { value: 0 }
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
2
3
4
5
6
7
Similar to properties on a reactive object, the .value property of a ref is reactive. In addition, when holding object types, ref automatically converts its .value with reactive().
A ref containing an object value can reactively replace the entire object:
const objectRef = ref({ count: 0 })
// this works reactively
objectRef.value = { count: 1 }
2
3
4
Refs can also be passed into functions or destructured from plain objects without losing reactivity:
const obj = {
foo: ref(1),
bar: ref(2)
}
// the function receives a ref
// it needs to access the value via .value but it
// will retain the reactivity connection
callSomeFunction(obj.foo)
// still reactive
const { foo, bar } = obj
2
3
4
5
6
7
8
9
10
11
12
# Writable Computed
Computed properties are by default getter-only. If you attempt to assign a new value to a computed property, you will receive a runtime warning. In the rare cases where you need a "writable" computed property, you can create one by providing both a getter and a setter :
<script setup>
import { ref, computed } from 'vue'
const firstName = ref('John')
const lastName = ref('Doe')
const fullName = computed({
// getter
get() {
return firstName.value + ' ' + lastName.value
},
// setter
set(newValue) {
// Note: we are using destructuring assignment syntax here.
;[firstName.value, lastName.value] = newValue.split(' ')
}
})
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Now when you run fullName.value = 'John Doe', the setter will be invoked and firstName and lastName will be updated accordingly.
don't make async requests or mutate the DOM inside a computed getter!
# Class and Style Bindings
If your component has multiple root elements, you would need to define which element will receive this class. You can do this using the $attrs component property:
<p :class="$attrs.class">Hi!</p>
<span>This is a child component</span>
<MyComponent class="baz" />
2
3
Will render:
<p class="baz">Hi!</p>
<span>This is a child component</span>
2
# Maintaining State with key
When Vue is updating a list of elements rendered with v-for, by default it uses an "in-place patch" strategy. If the order of the data items has changed, instead of moving the DOM elements to match the order of the items, Vue will patch each element in-place and make sure it reflects what should be rendered at that particular index.
This default mode is efficient, but only suitable when your list render output does not rely on child component state or temporary DOM state (e.g. form input values).
To give Vue a hint so that it can track each node's identity, and thus reuse and reorder existing elements, you need to provide a unique key attribute for each item:
<div v-for="item in items" :key="item.id">
<!-- content -->
</div>
2
3
# v-for with a Component
This section assumes knowledge of Components. Feel free to skip it and come back later.
You can directly use v-for on a component, like any normal element (don't forget to provide a key):
<MyComponent v-for="item in items" :key="item.id" />
However, this won't automatically pass any data to the component, because components have isolated scopes of their own. In order to pass the iterated data into the component, we should also use props:
<MyComponent
v-for="(item, index) in items"
:item="item"
:index="index"
:key="item.id"
/>
2
3
4
5
6
The reason for not automatically injecting item into the component is because that makes the component tightly coupled to how v-for works . Being explicit about where its data comes from makes the component reusable in other situations.
# Displaying Filtered/Sorted Results
Be careful with reverse() and sort() in a computed property! These two methods will mutate the original array, which should be avoided in computed getters. Create a copy of the original array before calling these methods:
- return numbers.reverse()
+ return [...numbers].reverse()
2
# Accessing Event Argument in Inline Handlers
Sometimes we also need to access the original DOM event in an inline handler. You can pass it into a method using the special $event variable, or use an inline arrow function:
<!-- using $event special variable -->
<button @click="warn('Form cannot be submitted yet.', $event)">
Submit
</button>
<!-- using inline arrow function -->
<button @click="(event) => warn('Form cannot be submitted yet.', event)">
Submit
</button>
2
3
4
5
6
7
8
9
function warn(message, event) {
// now we have access to the native event
if (event) {
event.preventDefault()
}
alert(message)
}
2
3
4
5
6
7
# Event Handling
# Key Modifiers
When listening for keyboard events, we often need to check for specific keys. Vue allows adding key modifiers for v-on or @ when listening for key events:
<!-- only call `vm.submit()` when the `key` is `Enter` -->
<input @keyup.enter="submit" />
2
You can directly use any valid key names exposed via KeyboardEvent.key as modifiers by converting them to kebab-case.
<input @keyup.page-down="onPageDown" />
In the above example, the handler will only be called if $event.key is equal to 'PageDown'.
# Key Aliases
Vue provides aliases for the most commonly used keys:
- .enter
- .tab
- .delete (captures both "Delete" and "Backspace" keys)
- .esc
- .space
- .up
- .down
- .left
- .right
# System Modifier Keys
You can use the following modifiers to trigger mouse or keyboard event listeners only when the corresponding modifier key is pressed:
- .ctrl
- .alt
- .shift
- .meta
On Macintosh keyboards, meta is the command key (⌘). On Windows keyboards, meta is the Windows key (⊞). On Sun Microsystems keyboards, meta is marked as a solid diamond (◆). On certain keyboards, specifically MIT and Lisp machine keyboards and successors, such as the Knight keyboard, space-cadet keyboard, meta is labeled “META”. On Symbolics keyboards, meta is labeled “META” or “Meta”.
For example:
<!-- Alt + Enter -->
<input @keyup.alt.enter="clear" />
<!-- Ctrl + Click -->
<div @click.ctrl="doSomething">Do something</div>
2
3
4
5
6
Note that modifier keys are different from regular keys and when used with keyup events, they have to be pressed when the event is emitted. In other words, keyup.ctrl will only trigger if you release a key while holding down ctrl. It won't trigger if you release the ctrl key alone.
# .exact Modifier
The .exact modifier allows control of the exact combination of system modifiers needed to trigger an event.
<!-- this will fire even if Alt or Shift is also pressed -->
<button @click.ctrl="onClick">A</button>
<!-- this will only fire when Ctrl and no other keys are pressed -->
<button @click.ctrl.exact="onCtrlClick">A</button>
<!-- this will only fire when no system modifiers are pressed -->
<button @click.exact="onClick">A</button>
2
3
4
5
6
7
8
# Mouse Button Modifiers
- .left
- .right
- .middle
These modifiers restrict the handler to events triggered by a specific mouse button.
# Form Input Bindings
When dealing with forms on the frontend, we often need to sync the state of form input elements with corresponding state in JavaScript. It can be cumbersome to manually wire up value bindings and change event listeners:
<input :value="text" @input="event => text = event.target.value" />
The v-model directive helps us simplify the above to:
<input v-model="text" />
In addition, v-model can be used on inputs of different types, <textarea>, and <select> elements. It automatically expands to different DOM property and event pairs based on the element it is used on:
- <input> with text types and <textarea> elements use value property and input event;
- and use checked property and change event;
v-model will ignore the initial value, checked or selected attributes found on any form elements. It will always treat the current bound JavaScript state as the source of truth. You should declare the initial value on the JavaScript side, using reactivity APIs.
# Modifiers
.lazy
By default, v-model syncs the input with the data after each input event (with the exception of IME composition as stated above). You can add the lazy modifier to instead sync after change events:
<!-- synced after "change" instead of "input" -->
<input v-model.lazy="msg" />
2
# Lifecycle Hooks
# Watchers
# Watch Source Types
watch's first argument can be different types of reactive "sources": it can be a ref (including computed refs), a reactive object, a getter function, or an array of multiple sources:
const x = ref(0)
const y = ref(0)
// single ref
watch(x, (newX) => {
console.log(`x is ${newX}`)
})
// getter
watch(
() => x.value + y.value,
(sum) => {
console.log(`sum of x + y is: ${sum}`)
}
)
// array of multiple sources
watch([x, () => y.value], ([newX, newY]) => {
console.log(`x is ${newX} and y is ${newY}`)
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Do note that you can't watch a property of a reactive object like this:
const obj = reactive({ count: 0 })
// this won't work because we are passing a number to watch()
watch(obj.count, (count) => {
console.log(`count is: ${count}`)
})
2
3
4
5
6
Instead, use a getter:
// instead, use a getter:
watch(
() => obj.count,
(count) => {
console.log(`count is: ${count}`)
}
)
2
3
4
5
6
7
# Deep Watchers
When you call watch() directly on a reactive object, it will implicitly create a deep watcher - the callback will be triggered on all nested mutations:
const obj = reactive({ count: 0 })
watch(obj, (newValue, oldValue) => {
// fires on nested property mutations
// Note: `newValue` will be equal to `oldValue` here
// because they both point to the same object!
})
obj.count++
2
3
4
5
6
7
8
9
This should be differentiated with a getter that returns a reactive object - in the latter case, the callback will only fire if the getter returns a different object:
watch(
() => state.someObject,
() => {
// fires only when state.someObject is replaced
}
)
2
3
4
5
6
You can, however, force the second case into a deep watcher by explicitly using the deep option:
watch(
() => state.someObject,
(newValue, oldValue) => {
// Note: `newValue` will be equal to `oldValue` here
// _unless_ state.someObject has been replaced
},
{ deep: true }
)
2
3
4
5
6
7
8
Use with Caution Deep watch requires traversing all nested properties in the watched object, and can be expensive when used on large data structures. Use it only when necessary and beware of the performance implications.
# watchEffect()
watch() is lazy: the callback won't be called until the watched source has changed. But in some cases we may want the same callback logic to be run eagerly - for example, we may want to fetch some initial data, and then re-fetch the data whenever relevant state changes. We may find ourselves doing this:
const url = ref('https://...')
const data = ref(null)
async function fetchData() {
const response = await fetch(url.value)
data.value = await response.json()
}
// fetch immediately
fetchData()
// ...then watch for url change
watch(url, fetchData)
2
3
4
5
6
7
8
9
10
11
12
This can be simplified with watchEffect(). watchEffect() allows us to perform a side effect immediately while automatically tracking the effect's reactive dependencies . The above example can be rewritten as:
watchEffect(async () => {
const response = await fetch(url.value)
data.value = await response.json()
})
2
3
4
Here, the callback will run immediately . During its execution, it will also automatically track url.value as a dependency (similar to computed properties). Whenever url.value changes, the callback will be run again.
watchEffect only tracks dependencies during its synchronous execution. When using it with an async callback, only properties accessed before the first await tick will be tracked.
# watch vs. watchEffect
watch and watchEffect both allow us to reactively perform side effects. Their main difference is the way they track their reactive dependencies:
watch only tracks the explicitly watched source. It won't track anything accessed inside the callback. In addition, the callback only triggers when the source has actually changed. watch separates dependency tracking from the side effect, giving us more precise control over when the callback should fire.
watchEffect, on the other hand, combines dependency tracking and side effect into one phase. It automatically tracks every reactive property accessed during its synchronous execution. This is more convenient and typically results in terser code, but makes its reactive dependencies less explicit.
# Callback Flush Timing
When you mutate reactive state, it may trigger both Vue component updates and watcher callbacks created by you.
By default, user-created watcher callbacks are called before Vue component updates. This means if you attempt to access the DOM inside a watcher callback, the DOM will be in the state before Vue has applied any updates.
If you want to access the DOM in a watcher callback after Vue has updated it, you need to specify the flush: 'post' option:
watch(source, callback, {
flush: 'post'
})
watchEffect(callback, {
flush: 'post'
})
2
3
4
5
6
7
Post-flush watchEffect() also has a convenience alias, watchPostEffect():
import { watchPostEffect } from 'vue'
watchPostEffect(() => {
// executed after Vue updates
})
2
3
4
5
# Stopping a Watcher
Watchers declared synchronously inside setup() or <script setup> are bound to the owner component instance, and will be automatically stopped when the owner component is unmounted. In most cases, you don't need to worry about stopping the watcher yourself.
The key here is that the watcher must be created synchronously: if the watcher is created in an async callback, it won't be bound to the owner component and must be stopped manually to avoid memory leaks. Here's an example:
<script setup>
import { watchEffect } from 'vue'
// this one will be automatically stopped
watchEffect(() => {})
// ...this one will not!
setTimeout(() => {
watchEffect(() => {})
}, 100)
</script>
2
3
4
5
6
7
8
9
10
11
To manually stop a watcher, use the returned handle function. This works for both watch and watchEffect:
const unwatch = watchEffect(() => {})
// ...later, when no longer needed
unwatch()
2
3
Note that there should be very few cases where you need to create watchers asynchronously, and synchronous creation should be preferred whenever possible. If you need to wait for some async data, you can make your watch logic conditional instead:
// data to be loaded asynchronously
const data = ref(null)
watchEffect(() => {
if (data.value) {
// do something when data is loaded
}
})
2
3
4
5
6
7
8
# Template Refs
While Vue's declarative rendering model abstracts away most of the direct DOM operations for you, there may still be cases where we need direct access to the underlying DOM elements. To achieve this, we can use the special ref attribute:
<input ref="input" />
ref is a special attribute, similar to the key attribute discussed in the v-for chapter. It allows us to obtain a direct reference to a specific DOM element or child component instance after it's mounted. This may be useful when you want to, for example,
programmatically focus an input on component mount, or initialize a 3rd party library on an element.
# Accessing the Refs
To obtain the reference with Composition API, we need to declare a ref with the same name :
<script setup>
import { ref, onMounted } from 'vue'
// declare a ref to hold the element reference
// the name must match template ref value
const input = ref(null)
onMounted(() => {
input.value.focus()
})
</script>
<template>
<input ref="input" />
</template>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Refs inside v-for
When ref is used inside v-for, the corresponding ref should contain an Array value, which will be populated with the elements after mount:
<script setup>
import { ref, onMounted } from 'vue'
const list = ref([
/* ... */
])
const itemRefs = ref([])
onMounted(() => console.log(itemRefs.value))
</script>
<template>
<ul>
<li v-for="item in list" ref="itemRefs">
{{ item }}
</li>
</ul>
</template>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
It should be noted that the ref array does not guarantee the same order as the source array.
# Ref on Component
ref can also be used on a child component. In this case the reference will be that of a component instance:
<script setup>
import { ref, onMounted } from 'vue'
import Child from './Child.vue'
const child = ref(null)
onMounted(() => {
// child.value will hold an instance of <Child />
})
</script>
<template>
<Child ref="child" />
</template>
2
3
4
5
6
7
8
9
10
11
12
13
14
If the child component is using Options API or not using <script setup>, the referenced instance will be identical to the child component's this, which means the parent component will have full access to every property and method of the child component. This makes it easy to create tightly coupled implementation details between the parent and the child, so component refs should be only used when absolutely needed
in most cases, you should try to implement parent / child interactions using the standard props and emit interfaces first.
An exception here is that components using <script setup> are private by default: a parent component referencing a child component using <script setup> won't be able to access anything unless the child component chooses to expose a public interface using the defineExpose macro:
<script setup>
import { ref } from 'vue'
const a = 1
const b = ref(2)
defineExpose({
a,
b
})
</script>
2
3
4
5
6
7
8
9
10
11