Introduction to Vue 3
Vue 3 is a JavaScript framework for building user interfaces. It is built on standard HTML, CSS, and JavaScript, and provides a declarative, component-based programming model to help you develop user interfaces efficiently. Whether it’s a simple or complex interface, Vue can do it all.
Get started quickly
Create a Vue 3 project
It is recommended to use in vue3
pnpm
. If it is not installedpnpm
, please install it globally first:npm
a>npm install -g pnpm
First, we need to create a new Vue 3 project via Vite
. You can install Vite with the following command:
pnpm create vite
# 如果没有 pnpm 可以使用 npm 安装 vite 也可以
# npm create vite@latest
Then follow the prompts!
You can also directly specify the project name and template you want to use via additional command line options. For example, to build a Vite + Vue project, run:
# npm 7+
npm create vite@latest my-vue-app -- --template vue
# yarn
yarn create vite my-vue-app --template vue
# pnpm
pnpm create vite my-vue-app --template vue
# bun
bunx create-vite my-vue-app --template vue
Application and component writing
In Vue3, we can use new组合式API
to create and manage components.
Here is a simpleVue3
component example:
<template>
<div>
<p>{
{
count }}</p>
<button @click="increment">Increment</button>
</div>
</template>
<script setup>
import {
ref } from 'vue'
const count = ref(0)
const increment = () => {
count.value++ }
</script>
We first imported the ref
function, and then used in the setup
function to create a reactive value< /span>. function that increments the value of . We also define a ref
count
increment
count
Base
Responsive Basics
Vue 3 provides two main functions to create reactive data:
ref
andreactive
.
Responsive principle
Vue3 implements getter/setter proxies for data through Proxy
to implement responsive data, providing better performance and more functions.
ref
In composite APIs, it is recommended to use the ref()
function to declare reactive state:
import {
ref } from 'vue'
const count = ref(0)
ref()
receives the parameter and wraps it in a ref object with the .value
attribute and returns:
const count = ref(0)
console.log(count) // { value: 0 }
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
reactive
There is another way to declare reactive state, using the reactive()
API. Unlike ref which wraps the internal value in a special object, reactive()
will make the object itself responsive:
import {
reactive } from 'vue'
const state = reactive({
count: 0 })
Use in template:
<button @click="state.count++">
{
{ state.count }}
</button>
Reactive objects are JavaScript proxies that behave just like normal objects. The difference is that Vue can intercept access to and modification of all properties of the reactive object in order to track dependencies and trigger updates.
reactive()
will convert objects deeply: when nested objects are accessed, they will also be wrapped by reactive()
. ref()
is also called internally when the value of ref is an object.
Recommended to use in actual development
ref
The difference between ref and reactive
ref
The main difference between and reactive
is that ref
is to allow basic types (such as numbers and strings) to become responsive, while a>reactive
is to make the object responsive.
ref
The responsive data created by needs to be accessed and modified through the .value
properties, while the responsive object created by reactive
can directly access and modify its properties. Therefore, ref
is better suited for handling primitive types, while reactive
is better suited for handling objects.
Composable API
The composite API is an important new feature of Vue 3. It provides a more flexible and logical way to organize and reuse code.
setup function
setup()
Hooks are the entry point to the composition API in a component and are called after the component instance is created and initialized, but before rendering occurs.
<script>
import {
ref } from 'vue'
export default {
setup() {
const count = ref(0)
// 返回值会暴露给模板和其他的选项式 API 钩子
return {
count
}
},
mounted() {
console.log(this.count) // 0
}
}
</script>
<template>
<button @click="count++">{
{ count }}</button>
</template>
setup
Syntactic sugar
<script setup>
import {
ref, onMounted } from 'vue'
const count = ref(0)
onMounted(() => {
console.log(this.count) // 0
})
</script>
<template>
<button @click="count++">{
{ count }}</button>
</template>
computed and watch
You can use computed
and watch
to create computed properties and monitor changes to reactive data.
It will be explained in detail below
<script setup>
import {
ref, computed, watch } from 'vue'
const count = ref(0)
const doubled = computed(() => count.value * 2)
watch(count, (newValue, oldValue) => {
console.log(`count changed from ${
oldValue} to ${
newValue}`)
})
</script>
life cycle hooks
- onMounted() ==> Executed after the component is mounted.
- onUpdated() ==> Called after the component updates its DOM tree due to a responsive state change.
- onUnmounted() ==> Called after the component instance is unmounted.
- onBeforeMount() ==> Called before the component is mounted.
- onBeforeUpdate() ==> Called just before the component updates its DOM tree due to reactive state changes.
- onBeforeUnmount() ==> Called before the component instance is unmounted.
- …
<script setup>
import {
onMounted, onUpdated, onUnmounted } from 'vue'
onMounted(() => {
console.log('component mounted')
})
onUpdated(() => {
console.log('component updated')
})
onUnmounted(() => {
console.log('component unmounted')
})
</script>
Custom hooks
You can create custom hooks to reuse code. A custom hook is a function that can use other reactive data and composite APIs.
definitionuseCounter.js
import {
ref, onMounted } from 'vue'
export function useCounter() {
const count = ref(0)
const increment = () => {
count.value++
}
onMounted(() => {
console.log('counter mounted')
})
return {
count,
increment
}
}
useuseCounter()
<script setup>
import {
useCounter } from "./useCounter"
const counter = useCounter()
</script>
Advanced
shallowReactive
reactive()
shallow form of action.
In some cases, you may want to create a shallow reactive object so that its internal properties are not converted to reactive.
import {
shallowReactive } from 'vue'
const state = shallowReactive({
count: 0 })
When using Echarts in vue3, when creating an Echarts instance, you can create it through the
shallowReactive
function
readonly
Accepts an object (either reactive or ordinary) or a ref, returning a read-only proxy with the original value.
You can use the readonly
function to create a read-only reactive object. Any attempt to modify a read-only object will raise an error in the development environment.
import {
readonly } from 'vue'
const original = reactive({
count: 0 })
const copy = readonly(original)
watchEffect(() => {
// 用来做响应性追踪
console.log(copy.count)
})
// 更改源属性会触发其依赖的侦听器
original.count++
// 更改该只读副本将会失败,并会得到一个警告
copy.count++ // warning!
toRefs
Converts a reactive object into a normal object. Each property of this normal object is a ref pointing to the corresponding property of the source object. Each individual ref is created using
toRef()
.
const state = reactive({
foo: 1,
bar: 2
})
const stateAsRefs = toRefs(state)
/*
stateAsRefs 的类型:{
foo: Ref<number>,
bar: Ref<number>
}
*/
// 这个 ref 和源属性已经“链接上了”
state.foo++
console.log(stateAsRefs.foo.value) // 2
stateAsRefs.foo.value++
console.log(state.foo) // 3
toRefs
is useful when returning reactive objects from composed functions. Using this, the consumer component can destructure/expand the returned object without losing responsiveness:
function useFeatureX() {
const state = reactive({
foo: 1,
bar: 2
})
// ...基于状态的操作逻辑
// 在返回时都转为 ref
return toRefs(state)
}
// 可以解构而不会失去响应性
const {
foo, bar } = useFeatureX()
toRefs
will only create refs for enumerable properties on the source object when called. If you want to create a ref for a property that may not exist yet, use toRef
instead.
Built-in components
Teleport
<Teleport>
Is a built-in component, the Teleport component allows you to render a child component anywhere in the DOM, not just its parent component.
<button @click="open = true">Open Modal</button>
<Teleport to="body">
<div v-if="open" class="modal">
<p>Hello from the modal!</p>
<button @click="open = false">Close</button>
</div>
</Teleport>
Suspense
<Suspense>
Is a built-in component used to coordinate the processing of asynchronous dependencies in the component tree. It allows us to wait higher up the component tree for multiple nested asynchronous dependencies below to be resolved, and render a loading state while waiting.
<Suspense>
<template #default>
<AsyncComponent />
</template>
<template #fallback>
<div>Loading...</div>
</template>
</Suspense>
Transition
<Transition>
It is a built-in component, which means it can be used in any other component without registration. It can apply entry and exit animations to elements or components passed to it through the default slot. Entry or exit can be triggered by one of the following conditions:
- Switch triggered by
v-if
- Switch triggered by
v-show
- Dynamic component switched by special element
<component>
- Change special
key
attributes
Here is an example of the most basic usage:
<button @click="show = !show">Toggle</button>
<Transition>
<p v-if="show">hello</p>
</Transition>
/* 下面我们会解释这些 class 是做什么的 */
.v-enter-active,
.v-leave-active {
transition: opacity 0.5s ease;
}
.v-enter-from,
.v-leave-to {
opacity: 0;
}
Other combination APIs
computed
computed
The function is used to create a reactive computed property. Accepts a getter function and returns a read-only reactive ref object. This ref exposes the return value of the getter function via.value
. It can also accept an object with theget
andset
functions to create a writable ref object.
Create a read-only computed property ref:
<script setup>
const count = ref(1)
const plusOne = computed(() => count.value + 1)
console.log(plusOne.value) // 2
plusOne.value++ // 错误
</script>
Create a writable computed property ref:
<script setup>
const count = ref(1)
const plusOne = computed({
get: () => count.value + 1,
set: (val) => {
count.value = val - 1
}
})
plusOne.value = 1
console.log(count.value) // 0
</script>
watchEffect
Run a function immediately while tracking its dependencies reactively and re-execute when dependencies change.
import {
ref, watchEffect } from 'vue'
const count = ref(0)
watchEffect(() => console.log(count.value))
// -> 输出 0
count.value++
// -> 输出 1
In this example, we use the watch
function to observe count
and print a message when its value changes. We also use the watchEffect
function to create a side effect that will be executed as soon as count
changes.
shallowReactive 和 shallowRef
shallowReactive
andshallowRef
allow us to create a shallow reactive object. ForshallowReactive
, only the first-level properties of the object will become responsive, and the deeper properties of the object will not be converted.shallowRef
is a shallow version ofref
which does not automatically unwrap the value inside.
import {
shallowReactive, shallowRef } from 'vue'
const obj = shallowReactive({
a: {
b: 1 } })
obj.a.b // 这不是一个响应式的值
const num = shallowRef(1)
num.value // 你需要使用 .value 才能访问到值
readonly 与 shallowReadonly
readonly
andshallowReadonly
allow us to create a read-only reactive object. Forreadonly
, all properties of the object, including nested properties, become read-only.shallowReadonly
is a shallow version ofreadonly
, only the first-level properties of the object will become read-only.
import {
readonly, shallowReadonly } from 'vue'
const obj = readonly({
a: {
b: 1 } })
obj.a = 2 // 这会抛出一个错误
const shallowObj = shallowReadonly({
a: {
b: 1 } })
shallowObj.a.b = 2 // 这不会抛出错误
provide and inject
provide
and inject
are Vue 3’s dependency injection API. A parent component acts as a dependency provider relative to all its descendant components. Any descendant component tree, no matter how deep, can injectdependencies provided by the parent component to the entire link.
import {
provide, inject } from 'vue'
// 在父组件中
provide('myValue', 123)
// 在子组件中
const myValue = inject('myValue') // myValue 现在是 123
toRaw and markRaw
toRaw
andmarkRaw
allow us to escape Vue’s reactive system.toRaw
returns the original version of an object, whilemarkRaw
prevents an object from being converted to reactive.
import {
reactive, toRaw, markRaw } from 'vue'
const obj = reactive({
a: 1 })
const rawObj = toRaw(obj) // rawObj 是 obj 的原始版本
const nonReactiveObj = markRaw({
a: 1 }) // nonReactiveObj 不会被转换为响应式的
customRef
customRef
Allows us to create a custom ref that we can control when it triggers dependency tracking and updates.
import {
customRef } from 'vue'
const myRef = customRef((track, trigger) => ({
get() {
track()
return someValue
},
set(newValue) {
someValue = newValue
trigger()
}
}))
isReactive
Checks whether an object is a proxy created by
reactive()
orshallowReactive()
.
isRef
Checks whether a value is a ref.
import {
reactive, ref, isReactive, isRef } from 'vue'
const obj = reactive({
a: 1 })
isReactive(obj) // true
const num = ref(1)
isRef(num) // true
BecauseisRef
the return value is atype predicate (type predicate), which meansisRef
can be used as a type guard:
let foo: unknown
if (isRef(foo)) {
// foo 的类型被收窄为了 Ref<unknown>
foo.value
}
Dive into reactive systems
Vue 3's reactive system is built on a function called effect
which is used to collect dependencies (dependency tracking) and trigger side effects. When a property of a reactive object is accessed, effect
collects it as a dependency; when a property of a reactive object is modified, it will trigger the associated side effects.
effect、reactive、ref
reactive
and ref
are two basic reactive APIs of Vue 3. They both use effect
to track dependencies and trigger updates.
import {
effect, reactive, ref } from 'vue'
// 使用 reactive
const state = reactive({
a: 1 })
effect(() => {
console.log(state.a)
})
state.a = 2 // 2
// 使用 ref
const count = ref(0)
effect(() => {
console.log(count.value)
})
count.value++ // 1
In this example, we create a reactive object and a ref, and then use effect
to create two side effects that print out the values of the object and ref respectively. When these values are changed, side effects are triggered.
track、trigger
track
and trigger
are the underlying APIs of Vue 3, which are used to collect dependencies and trigger updates respectively.
import {
reactive, effect, track, trigger } from 'vue'
const state = reactive({
a: 1 })
effect(() => {
// 手动进行依赖追踪
track(state, 'a')
console.log(state.a)
})
state.a = 2 // 打印 "2"
// 手动触发更新
trigger(state, 'a')
In this example, we manually collected as a dependency using track
and then manually triggered it using updated. state.a
trigger
Nested structure processing
Vue 3’s reactive system can handle nested reactive objects.
import {
reactive, effect } from 'vue'
const state = reactive({
user: {
name: 'Alice'
}
})
effect(() => {
console.log(state.user.name)
})
state.user.name = 'Bob' // 打印 "Bob"
In this example, we create a nested reactive object and use effect
to create a side effect that prints out the user's name. The side effect is triggered when the user's name is changed.
In-depth compilation optimization
Vue 3 has made great improvements in compilation optimization. During the compilation process, Vue 3 performs static analysis on the template, extracts the parts that will not change, and pre-compiles it into pure JavaScript, which greatly improves the rendering efficiency at runtime. These optimizations are described in detail below.
Static node promotion
In Vue 3, if there are parts of your template that will not change, for example:
<template>
<div>
<h1>Hello, world!</h1>
<p>Welcome to Vue 3</p>
</div>
</template>
At compile time, the static nodes "Hello, world!" and "Welcome to Vue 3" will be promoted, avoiding the need to recreate them every time you render.
Use fragments and templates together
In Vue 3, you can have multiple root nodes in a component template, which are fragments:
<template>
<header>Header</header>
<main>Main content</main>
<footer>Footer</footer>
</template>
In this example we have three root nodes, which is not allowed in Vue 2. But in Vue 3, this is completely normal.
dynamic compilation
Vue 3's dynamic compilation allows us to compile template strings at runtime. For example, we can dynamically create a Hello组件
:
import {
compile, h } from 'vue'
const template = `<h1>{
{ greeting }} World!</h1>`
const render = compile(template)
export default {
data() {
return {
greeting: 'Hello',
}
},
render
}
In this example, we compile the template string at runtime and set the result into the component's render function. This technique is useful in scenarios where templates need to be dynamically generated, such as rendering user-supplied templates in a CMS.
In-depth componentization
Vue 3 has also made a lot of progress in componentization, which will be introduced in detail below.
dynamic components
Vue 3 supports using the component
tag to dynamically load components. The component can be a component options object or the name of a component. This feature is useful when displaying different components based on different states.
<component :is="currentComponent"></component>
The value passed to :is
can be one of the following:
- Registered component name
- Imported component object
Asynchronous components
Vue 3 supports asynchronous components. You can use the defineAsyncComponent
method to define an asynchronous component. This method receives a factory function that returns a Promise. The Promise needs to be resolved into a component.
import {
defineAsyncComponent } from 'vue'
const AsyncComp = defineAsyncComponent(() =>
import('./components/MyComponent.vue')
)
High-order components
Higher-Order Component (HOC for short) is a design pattern that is a function that receives a component and returns a new component. In Vue 3, you can use the composition API to create higher-order components more easily.
import {
ref } from 'vue'
export default function withHover(Component) {
return {
setup(props, {
slots }) {
const isHovered = ref(false)
return () => (
<div
onMouseenter={
() => (isHovered.value = true)}
onMouseleave={
() => (isHovered.value = false)}
>
<Component {
...props} isHovered={
isHovered.value} v-slots={
slots} />
</div>
)
}
}
}
In this example, the withHover
function receives a component and returns a new component. The new component has a isHovered
attribute indicating whether the mouse Hover over the component. This pattern helps us reuse logic across different components.
Render function
In Vue 3, the Render function is an advanced feature that provides greater flexibility. Although Vue's template system is powerful enough, in some cases it is more convenient to write rendering logic directly in JavaScript.
The working principle of the Render function is to tell Vue how to render the interface by returning a virtual node (VNode). Vue 3 provides the h
function for creating VNode.
import {
h } from 'vue'
export default {
render() {
return h('div', {
}, 'Hello, world!')
}
}
In this example, we create a element using the h
function and then return it in the Render function. div
h()
detailsThe first parameter can be either a string (for native elements) or a Vue component definition. The second parameter is the prop to be passed, and the third parameter is the child node.
When creating a component's vnode, child nodes must be passed in slot functions. If the component only has default slots, it can be passed using a single slot function. Otherwise, it must be passed as an object of the slot function.
For readability, the prop parameter can be omitted when the child node is not a slot object.
Compilation optimization
Vue 3's compiler does many optimizations at compile time, such as static node promotion and dynamic node binding, which reduces unnecessary work at runtime. Static node promotion takes nodes that don't change out of the render function, thus avoiding having to recreate them on every render. Dynamic node binding optimizes nodes that may change. Only when the binding values of these nodes change, the nodes will be re-rendered.
Manually write rendering logic
Sometimes, we may need to write rendering logic manually. For example, when we need to dynamically generate a list based on a set of data, we can use the JavaScript array method in the Render function.
import {
h } from 'vue'
export default {
data() {
return {
items: ['Apple', 'Banana', 'Cherry']
}
},
render() {
return h('div', {
}, this.items.map(item => h('div', {
}, item)))
}
}
In this example, we use the map
method to dynamically generate the elements of a list, and then return it in the Render function.
The Render function provides a powerful way to control the rendering process of Vue applications, allowing us to better control and optimize application performance.
vue3 ecology
Status Management - Pinia
Pinia | The intuitive store for Vue.js
Pinia
Similar to Vuex, but simpler and easier to use in API design.
Pinia
The main advantages include:
- It has a cleaner API and reduces the amount of boilerplate code.
- It provides better type support through TypeScript.
- It provides component-based state storage, loading state only when needed.
Route management - Vue Router
Vue Router | The official router for Vue.js
Vue Router is a routing library for Vue.js that you can use to build single-page applications, as shown in the following example:
First, create a router:
import {
createRouter, createWebHistory } from 'vue-router'
import Home from './components/Home.vue'
import About from './components/About.vue'
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/', component: Home },
{
path: '/about', component: About }
]
})
Then, use this router in your Vue application:
import {
createApp } from 'vue'
import App from './App.vue'
import router from './router'
createApp(App).use(router).mount('#app')
Finally, use <router-link>
and <router-view>
in the component to navigate and render the route:
<template>
<router-link to="/">Home</router-link>
<router-link to="/about">About</router-link>
<router-view></router-view>
</template>
UI Framework - Element Plus
A Vue 3 UI framework | Element Plus
Element Plus is a UI framework developed for Vue.js. It provides a rich and diverse set of components that can help us build beautiful interfaces more easily:
<template>
<el-button type="primary">主要按钮</el-button>
<el-button type="success">成功按钮</el-button>
<el-button type="warning">警告按钮</el-button>
</template>
Test Plan - Vitest
Vitest | Next generation testing framework
Vitest is a testing framework that natively supports Vite. Very fast!
import {
assert, describe, it } from 'vitest'
describe.skip('skipped suite', () => {
it('test', () => {
// 已跳过此测试套件,无错误
assert.equal(Math.sqrt(4), 3)
})
})
describe('suite', () => {
it.skip('skipped test', () => {
// 已跳过此测试,无错误
assert.equal(Math.sqrt(4), 3)
})
})
Other changes
In Vue 3, developers will notice some important changes, mainly reflected in the transfer of global APIs and better support for TypeScript.
Fragment
In Vue 3, you can have multiple root nodes in a component's template, this is called Fragment
.
<template>
<div>Hello</div>
<div>World</div>
</template>
Global API transfer
In Vue 3, some global APIs have been moved to globalProperties
, for example, Vue.prototype
became < in Vue 3 a i=3>. This is done to better isolate the global API and provide greater flexibility for possible future changes. app.config.globalProperties
For example, originally in Vue 2 we might add a global method like this:
Vue.prototype.$myGlobalMethod = function () {
return 'Hello World!'
}
In Vue 3 we need to do this:
const app = createApp(App)
app.config.globalProperties.$myGlobalMethod = function () {
return 'Hello World!'
}
Then we can use this method in any component:
this.$myGlobalMethod()
Removed API
Vue 3 In order to simplify the framework and avoid future maintenance burdens, some APIs that have been deprecated in Vue 2 have been deleted, such as Vue.set
, Vue.delete
andVue.observable
.
TypeScript support
Vue 3 has been rewritten internally using TypeScript from the beginning, so there is a significant improvement in TypeScript support. This includes better type inference, auto-completion, and stronger type safety.
For example, in Vue 2, we may need to use Vue.extend() or @Component
decorator to ensure that the TypeScript type is correct, but in Vue 3, we can directly use < /span>defineComponent
method, which correctly infers the component type:
import {
defineComponent } from 'vue'
export default defineComponent({
// type inference enabled
})