It’s 2023 and you still don’t know Vue3? A note to get you started quickly

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 vue3pnpm. 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 refcountincrementcount

Base

Responsive Basics

Vue 3 provides two main functions to create reactive data: ref and reactive.

Responsive principle

dataVue3 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 developmentref

The difference between ref and reactive

refThe 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.

refThe 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

Composition APIThe 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>

setupSyntactic 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

Component life cycle diagram

  • 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

computedThe 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 the get and set 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 and shallowRef allow us to create a shallow reactive object. For shallowReactive, 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 of ref 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 and shallowReadonly allow us to create a read-only reactive object. For readonly, all properties of the object, including nested properties, become read-only. shallowReadonly is a shallow version of readonly, 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/inject pattern

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 and markRaw allow us to escape Vue’s reactive system. toRaw returns the original version of an object, while markRaw 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

customRefAllows 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() or shallowReactive().

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.atrigger

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

render pipelineIn 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()details

The 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

PiniaSimilar to Vuex, but simpler and easier to use in API design.

PiniaThe main advantages include:

  1. It has a cleaner API and reduces the amount of boilerplate code.
  2. It provides better type support through TypeScript.
  3. 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
})

Guess you like

Origin blog.csdn.net/weixin_60053942/article/details/134997971