-
项目启动
Vue2:new Vue() 创建应用 Vue.use() 使用插件 Vue.component() 注册全局组件 Vue3: // 创建一个vue应用 // 1. 导入createApp函数 // 2. 编写一个根组件App.vue,导入进来 // 3. 基于根组件创建应用实例 // 4. 挂载到index.html的#app容器 import {createApp} from 'vue' import App from './App.vue' const app = createApp(App) app.mount('#app')
-
生命周期
对于生命周期来说,整体上变化不大,只是大部分生命周期钩子名称上 + “on”,功能上是类似的。不过有一点需要注意,Vue3 在组合式API(Composition API,下面展开)中使用生命周期钩子时需要先引入,而 Vue2 在选项API(Options API)中可以直接调用生命周期钩子,如下所示。
// vue3 <script setup> import { onMounted } from 'vue'; // 使用前需引入生命周期钩子 onMounted(() => { // ... }); // 可将不同的逻辑拆开成多个onMounted,依然按顺序执行,不会被覆盖 onMounted(() => { // ... }); </script> // vue2 <script> export default { mounted() { // 直接调用生命周期钩子 // ... }, } </script>
常用生命周期对比如下表所示。

setup 是围绕 beforeCreate 和 created 生命周期钩子运行的,所以不需要显式地去定义。
import { defineComponent, onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted, onErrorCaptured, onRenderTracked, onRenderTriggered, } from "vue"; export default defineComponent({ //beforeCreate和created是vue2的 beforeCreate() { console.log("------beforeCreate-----"); }, created() { console.log("------created-----"); }, setup() { console.log("------setup-----"); // vue3.x生命周期写在setup中 onBeforeMount(() => { console.log("------onBeforeMount-----"); }); onMounted(() => { console.log("------onMounted-----"); }); onBeforeUpdate(() => { console.log("------onBeforeUpdate-----"); }); onUpdated(() => { console.log("------onUpdated-----"); }); onBeforeUnmount(() => { console.log("------onBeforeUnmount-----"); }); onUnmounted(() => { console.log("------onUnmounted-----"); }); onErrorCaptured(() => { console.log("------onErrorCaptured-----"); }); onRenderTracked(() => { console.log("------onRenderTracked-----"); }); // 调试哪些数据发生了变化 onRenderTriggered((event) => { console.log("------onRenderTriggered-----", event); }); }, });
-
多根节点
// vue2中在template里存在多个根节点会报错 <template> <header></header> <main></main> <footer></footer> </template> // 只能存在一个根节点,需要用一个<div>来包裹着 <template> <div> <header></header> <main></main> <footer></footer> </div> </template> // 但是,Vue3 支持多个根节点,也就是 fragment。即以下多根节点的写法是被允许的。 <template> <header></header> <main></main> <footer></footer> </template>
-
Options API 与 Composition API
Vue 组件可以用两种不同的 API 风格编写:Options API 和 Composition API。
-
Options API
使用 Options API,我们使用选项对象定义组件的逻辑,例如data、methods和mounted。由选项定义的属性在 this 内部函数中公开,指向组件实例,如下所示。
<template> <button @click="increment">count is: {{ count }}</button> </template> <script> export default { data() { return { count: 0 } }, methods: { increment() { this.count++; } }, mounted() { console.log(`The initial count is ${this.count}.`); } } </script>
-
Composition API
使用 Composition API,我们使用导入的 API 函数定义组件的逻辑。在 SFC 中,Composition API 通常使用
<template> <button @click="increment">Count is: {{ count }}</button> </template> <script setup> import { ref, onMounted } from 'vue'; const count = ref(0); function increment() { count.value++; } onMounted(() => { console.log(`The initial count is ${count.value}.`); }) </script>
-
异步组件(Suspense)
Vue3 提供 Suspense 组件,允许程序在等待异步组件加载完成前渲染兜底的内容,如 loading ,使用户的体验更平滑。使用它,需在模板中声明,并包括两个命名插槽:default 和 fallback。Suspense 确保加载完异步内容时显示默认插槽,并将 fallback 插槽用作加载状态。
<tempalte> <suspense> <template #default> <List /> </template> <template #fallback> <div> Loading... </div> </template> </suspense> </template>
在 List 组件(有可能是异步组件,也有可能是组件内部处理逻辑或查找操作过多导致加载过慢等)未加载完成前,显示 Loading...(即 fallback 插槽内容),加载完成时显示自身(即 default 插槽内容)。
-
Teleport
Vue3 提供 Teleport 组件可将部分 DOM 移动到 Vue app 之外的位置。比如项目中常见的 Dialog 弹窗。
<button @click="dialogVisible = true">显示弹窗</button> <teleport to="body"> <div class="dialog" v-if="dialogVisible"> 我是弹窗,我直接移动到了body标签下 </div> </teleport>
-
路由
使用 createRouter 工厂函数来创建路由实例,然后将其传递给根组件使用:
import { createRouter, createWebHistory } from 'vue-router' import HelloWorld from './components/HelloWorld.vue' const routes = [ { path: '/', component: HelloWorld } ] const router = createRouter({ history: createWebHistory(), routes }) export default router
-
请求
在 Vue 3.0 中,官方推荐的 HTTP 请求库是 axios 的替代品—— vite-plugin-mock,该库内置了一套基于 axios 的请求拦截和响应拦截机制,并且已经在 Vite 中默认启用了。这种方式可以大大减少编写重复代码的工作量,同时也支持更好的类型推断和类型校验(使用 async/await 来封装请求代码)。
-
代码上
import组件
<template> <Chart /> </template> <script setup> // 必须将后缀补全 // 一个import即可,无需在组件属性中添加,因为<script setup> 会自动把组件注册到当前组件 import Chart from './chart.vue' </script>
Vue2不能v-if v-for 同时使用,因为v-for会先执行,但Vue3可以:
<div v-if="show"> <div v-for="item in items" :key="item.id">{{ item }}</div> </div>
Vue3 v-bind可以省略
v-model :
<input v-model="count" type="number"> <!-- 等价于 --> <input :value="count" @input="count = $event.target.value" type="number”> <el-dialog v-model:visible="isVisible" v-model:content="content"></el-dialog> <!-- 相当于 --> <el-dialog :visible="isVisible" :content="content" @update:visible="isVisible" @update:content="content" ></el-dialog> 摒弃 .sync ,直接使用v-model
定义全局变量:
// 之前(Vue 2.x) Vue.prototype.$http = () => {} Vue.prototype.url= 'http://123' // 之后(Vue 3.x) const app = createApp({}) app.config.globalProperties.$http = () => {} app.config.globalProperties.url= 'http://123'
插槽slot:
// vue2.0使用插槽基本上直接使用slot进行操作,其中vue2.0经历了两次更迭,2.6.0版本slot升级为v-slot <div> <slot :current="toolTipData" name="test"></slot> // 具名 作用域插槽 <slot></slot> //默认插槽 </div> //父组件调用该组件 <test> <template> <div>默认插槽</div> </template> // 作用域插槽 <template slot-scope="{ current }" slot="test"> <el-form label-width="80px" :model="current"> <el-form-item label="id:"> <el-link type="info">{{ current.id }}</el-link> </el-form-item> </el-form> </template> </test> //==============vue3.0使用插槽 //在vue3.0中,插槽使用v-slot 简写用# <div> <slot name="test" :newData="slotsData"></slot> <slot></slot> </div> <HelloWorld msg="Welcome to Your Vue.js + TypeScript App"> <template #default> // 可以写为v-slot:default #后面跟的是插槽名称 <div>默认插槽</div> </template> //作用域插槽 <template #test="{ newData }"> // 可以写为v-slot:test="newData" <p>{{ newData.aa }}</p> <p>{{ newData.bb }}</p> </template> </HelloWorld> //一个组件里面具有多个插槽时,一定要带上名称,否则可能会导致作用域错乱
toRefs 用于将一个 reactive 对象转化为属性全部为 ref 对象的普通对象:
<template> <div> <p>计数:{{ num }}s</p> <p>主人年龄:{{ person.age }}</p> <p>主人姓名:{{ person.name }}</p> <p>动物类别:{{ atype }}</p> <p>动物名称:{{ aname }}</p> <p>动物年龄:{{ aage }}</p> </div> </template> <script> import { defineComponent, reactive, ref, toRefs } from "vue"; export default defineComponent({ setup() { //使用ref声明基本类型 const num = ref(0); //使用ref声明对象 const person = ref({ age: 20, name: "张三" }); //使用reactive声明对象 const animal = reactive({ atype: "猫", aname: "小花", aage: 5 }); setTimeout(() => { person.value.age = person.value.age + 1; person.value.name = "李四"; animal.aage++; }, 1000); setInterval(() => { num.value++; }, 1000); return { num, person, ...toRefs(animal), }; }, }); </script>
Readonly只读变量:
import { reactive, readonly } from 'vue' const original = reactive({ count: 0 }) const copy = readonly(original) // 通过 original 修改 count,将会触发依赖 copy 的侦听器 original.count++ // 通过 copy 修改 count,将导致失败并出现警告 copy.count++ // 警告: "Set operation on key 'count' failed: target is readonly."
watch监听:
//监听reactive对象: watch( () => animal.aage, (curAge, preAge) => { console.log("新值:", curAge, "老值:", preAge); } ); //监听ref变量 watch(num, (newVal, oldVal) => { console.log("新值:", newVal, "老值:", oldVal); }); //多个值的监听 watch([() => animal.aage, num], ([curAge, newVal], [preAge, oldVal]) => { console.log("新值:", curAge, "老值:", preAge); console.log("新值:", newVal, "老值:", oldVal); }); //监听对象复杂时,请使用深度监听 让函数的第三个参数为deep:true watch( () => state.animal, (newType, oldType) => { console.log("新值:", newType, "老值:", oldType); }, { deep: true } ); //停止监听函数 const stopWatchRoom = watch( () => state.animal, (newType, oldType) => { console.log("新值:", newType, "老值:", oldType); }, { deep: true } ); setTimeout(() => { // 停止监听 stopWatchRoom(); }, 3000);
默认情况下,watch 是惰性的, 给第三个参数中设置immediate: true即可立即执行回调函数;