Vue3 -- 基础语法
0. V2 和 V3 的API 对比
1. OptionsAPI 和 CompositionAPI
V2的API设计是Options (配置)风格V3的API设计是Composition (组合)风格
2. Options API 的弊端
Options 类型的 API, 其数据、方法、计算属性等, 是分散在 data 、methods 、computed 中的, 若想要新增或者修改一个需求, 则需要分别修改 data 、methods 、computed, 不便于维护和复用
3. Composition API 的优势
使用传统OptionsAPI中,新增或者修改一个需求,就可能需要分别在data.methods,computed里都修改一遍,代码太分散
使用Composition API 可以更加优雅的组织代码,函数,让相同功能的代码更加有序的组织在一起
1. setup()
0. 概述和注意项
Vue3.0中的一个新的配置项,值为一个函数,setup是所有的 Composition API 表演的舞台,组件中所用到的数据、方法等等,均要配置在setup中
setup的两种返回值
- 若返回一个对象,则对象中的属性,方法在模版中均可以直接使用
- 若返回一个渲染函数,则可以自定义渲染内容
注意点
- 尽量不要与Vue2.x配置混匀
- Vue2.x配置的(data,methods,computed..)中可以访问到setup中的属性和方法
- 但在setup中不能访问到Vue2.x配置的(data,methods,computed..)
- 如果有vue2定义的数据和vue3定义的数据重名,setup优先
- setup的异步必须和Suspense和异步组件配合使用,否则会因为加了async函数后返回值不再是return的对象,而是promise,模版看不到return对象中的属性
1. 执行时机
在 beforeCreate() 前执行一次,此时的 this 是 undefined
2. 参数
- props: 值为对象,包含: 组件外部传递过来,且在组件内使用
props:["a","b","c"]声明接收了的属性 - context: 上下文对象
- context.attrs: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性,相当于
this.$attrs - context.slots: 接收的插槽内容,相当于
this.$slots - context.emit: 触发自定义事件的函数,相当于
this.$emit
- context.attrs: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性,相当于
3. 代码示例
1. 返回对象(常用)
App.vue
<template>
<div class="app">
<h2>姓名: {{ name }}</h2>
<h2>年龄: {{ age }}</h2>
<button @click="showName">展示名字</button>
<button @click="changeAge">修改年龄</button>
</div>
</template>
<script lang="ts">
export default {
name: 'Person',
setup(){
// 数据 -- 对应 vue2 中在 data 中定义的数据
let name = "小明"
let age = 18
// 方法 -- 对应 vue2 中在 methods 中定义的方法
function showName(){
alert(name)
}
function changeAge(){
age += 1 // 由于上面定义的 age 不是响应式, 虽然内存中修改了 age 的值, 但是页面中并没有改变, 需要使用 响应式加工函数 来定义数据
}
// 必须返回上面定义的数据和方法, 页面中才能读取到这些数据和方法
return {
name,
age,
showName
}
}
}
</script>
<!-- scoped 表示局部生效 -->
<style scoped>
.app{
background-color: aqua;
}
</style>
2. 返回渲染函数(不常用)
App.vue
<template>
<button @click="sayHello">点击弹窗</button>
</template>
<script>
import {h} from 'vue'
export default {
name: 'App',
setup() {
// 返回渲染函数(不常用)
return () => {
return h("h1","小明")
}
}
}
</script>
<style>
</style>
4. 语法糖
配置语法糖后, 不再需要手动 return
<template>
<div class="app">
<h2>姓名: {{ name }}</h2>
<h2>年龄: {{ age }}</h2>
<button @click="showName">展示名字</button>
<button @click="changeAge">修改年龄</button>
</div>
</template>
<script lang="ts">
export default {
name: 'Person'
}
</script>
<!-- 新增一个 script 脚本, 并且标记为 setup , lang="ts", 用来配置组合式API, 它会自动 return 脚本中定义好的数据和方法-->
<script setup lang="ts">
// 数据 -- 对应 vue2 中在 data 中定义的数据
let name = "小明"
let age = 18
// 方法 -- 对应 vue2 中在 methods 中定义的方法
function showName(){
alert(name)
}
function changeAge(){
age ++
}
</script>
<style>
.app{
background-color: aqua;
}
</style>
5. 功能插件
如果文件名就是组件名, 可以不写第一个 script 脚本, 并且不下载当前功能插件,
如果需要可以单独配置组件名, 且想将两个 script 脚本合并, 则需要第三方的插件实现
1. 下载插件
npm i vite-plugin-vue-setup-extend -D
2. 配置插件
vite.config.ts
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// 1. 引入插件,名字可以随便起
import VueSetupExtend from 'vite-plugin-vue-setup-extend'
export default defineConfig({
plugins: [
vue(),
VueSetupExtend() // 2. 注册插件
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})
3. 合并 script 代码示例
<template>
<div class="app">
<h2>姓名: {{ name }}</h2>
<h2>年龄: {{ age }}</h2>
<button @click="showName">展示名字</button>
<button @click="changeAge">修改年龄</button>
</div>
</template>
<!-- 通过指定 name="Person" 来指定当前组件的名字 -->
<script setup lang="ts" name="Person">
// 数据 -- 对应 vue2 中在 data 中定义的数据
let name = "小明"
let age = 18
// 方法 -- 对应 vue2 中在 methods 中定义的方法
function showName(){
alert(name)
}
function changeAge(){
age ++
}
</script>
<style>
.app{
background-color: aqua;
}
</style>
2. 响应式加工函数
1. ref( )
用来加工数据为响应式,得到引用对象, ref 所定义的响应式数据需要 .value 来读取和修改, 如果是用 vscode 来编写代码, 可以使用 volar 插件来自动添加.value 左下角的齿轮图标 => 设置 => 扩展 => volar => 勾选 Auto Insert: Dot Value
1. 使用 ref 加工基本类型
<template>
<div class="app">
<h2>姓名: {{ name }}</h2>
<h2>年龄: {{ age }}</h2> <!-- 5. Vue3在解析模版的时候发现name是ref对象会自动.value取值 -->
<button @click="showName">展示名字</button>
<button @click="changeAge">修改年龄</button>
</div>
</template>
<script setup lang="ts" name="Person">
// 1. 引入 ref
import { ref } from 'vue'
let name = "小明"
let age = ref(18) // 2. 使用 ref 定义想要实现响应式的数据
function showName() {
alert(name)
}
function changeAge() {
console.log(age.value) // 3. 使用 name.value 来读取响应式数据的值
age.value += 1 // 4. 使用 age.value 来修改响应式数据的值
}
</script>
<style>
.app {
background-color: aqua;
}
</style>
2. 使用 ref 加工复杂类型
<template>
<div class="app">
<!-- 5. 使用 car.brand 来显示响应式的对象数据 -->
<h2>品牌: {{ car.brand }}</h2>
<h2>价格: {{ car.price }} 万</h2>
<button @click="changeBrand">修改品牌</button>
<button @click="changePrice">修改价格</button>
<button @click="changeObj">修改对象</button>
</div>
</template>
<script setup lang="ts" name="Person">
// 1. 引入 ref
import {ref} from "vue";
// 2. 使用 ref 定义想要实现响应式的对象数据
let car = ref({
brand: "奔驰",
price: 100
})
function changeBrand() {
car.value.brand = "小米"
}
function changePrice() {
console.log(car.value.price) // 3. 使用 car.value.price 来读取响应式数据
car.value.price += 50 // 4. 使用 car.value.brand 来修改响应式数据
}
function changeObj(){
car.value = {brand: "奥拓",price: 5} // 5. 使用 car.value 来重新分配对象, 不会像 reactive() 一样必须使用 Object.assign()
}
</script>
<style scoped>
.app {
background-color: aqua;
}
</style>
2. reactive() --- 只能加工复杂类型数据
只能定义对象类型的响应数据,基本数据类型使用ref(),得到proxy对象
1. 基本使用
<template>
<div class="app">
<!-- 5. 使用 car.brand 来显示响应式的对象数据 -->
<h2>品牌: {{ car.brand }}</h2>
<h2>价格: {{ car.price }} 万</h2>
<button @click="changeBrand">修改品牌</button>
<button @click="changePrice">修改价格</button>
</div>
</template>
<script setup lang="ts" name="Person">
// 1. 引入 reactive
import {reactive} from "vue";
// 2. 使用 reactive 定义想要实现响应式的对象数据
let car = reactive({
brand: "奔驰",
price: 100
})
function changeBrand() {
car.brand = "小米"
}
function changePrice() {
car.price += 50 // 3. 使用 car.brand 来修改响应式数据
console.log(car.price) // 4. 使用 car.price 来读取响应式数据
}
</script>
<style scoped>
.app {
background-color: aqua;
}
</style>
2. 多属性对象
简写形式, 将所有的数据类型放在一个 对象 中, 并用 reactive 将其定义为 响应式数据
<template>
<h3>姓名{{person.name}}</h3>
<h3>年龄{{person.age}}</h3>
<h3>工作{{person.job.type}}</h3>
<h3>薪水{{person.job.salary}}</h3>
<ul>
<li v-for="(h,i) in person.hobby" :key="i">{{h}}</li>
</ul>
<button @click="changeInfo">点击修改人的信息</button>
</template>
<script>
// 1. 引入reactive
import {reactive} from 'vue'
export default {
name: 'App',
setup() {
// 用 reactive 定义一个对象类型数据的proxy代理整个 person 对象,其中含很多想要的数据,和vue2中的data定义形式类似
let person = reactive({
name: "张三",
age: 18,
job: {
type: "前端工程师",
salary: "30k"
},
hobby: ["抽烟", "喝酒", "烫头"]
})
function changeInfo() {
person.name = "小明"
person.age = 20
person.job.type = "后端工程师"
person.job.salary = "50k"
person.hobby[0] = "吃饭"
}
// 返回对象
return {
person,
changeInfo
}
}
}
</script>
<style>
</style>
3. reactive() 的坑
当 被reactive定义的响应对象被重新分配新对象时, 会 失去 响应式 (可以使用 Object.assign() 去整体替换)
<template>
<div class="app">
<!-- 5. 使用 car.brand 来显示响应式的对象数据 -->
<h2>品牌: {{ car.brand }}</h2>
<h2>价格: {{ car.price }} 万</h2>
<button @click="changeObj">修改对象</button>
</div>
</template>
<script setup lang="ts" name="Person">
// 1. 引入 reactive
import {reactive} from "vue";
// 2. 使用 reactive 定义想要实现响应式的对象数据
let car = reactive({
brand: "奔驰",
price: 100
})
function changeObj(){
// car = {brand: "奥拓",price: 5} // 不能直接赋值给响应式数据 car, 这样会导致 car 失去响应式
Object.assign(car,{brand: "奥拓",price: 5}) // 使用 Object.assign 来整体替换,以保持它的响应式
}
</script>
<style scoped>
.app {
background-color: aqua;
}
</style>
4. toRef()
创建一个 ref 对象,其value值指向另一个对象中的某个属性值, 应用于将响应式对象中的某个属性单独提供给外部使用时
const name = toRef(person,"name")
代码示例
<template>
<div class="app">
<!-- 8. 使用 car.brand 来显示响应式的对象数据 -->
<h2>品牌: {{ car.brand }}</h2>
<h2>价格: {{ car.price }} 万</h2>
<button @click="changeBrand">修改品牌</button>
<button @click="changePrice">修改价格</button>
<button @click="changeObj">修改对象</button>
</div>
</template>
<script setup lang="ts" name="Person">
// 1. 引入 ref
import {reactive, toRef, toRefs} from "vue";
// 2. 使用 reactive 定义想要实现响应式的对象数据
let car = reactive({
brand: "奔驰",
price: 100
})
// 3. 将由 reactive 定义的每一组响应式数据取出, 使用 toRef 来响应式的代理, 并赋值给 brand, 此时的 brand 也是响应式数据
let brand = toRef(car,"brand")
// 4. 将由 reactive 定义的每一组响应式数据取出, 使用 toRef 来响应式的代理, 并赋值给 price, 此时的 price 也是响应式数据
let price = toRef(car,"price")
function changeBrand() {
brand.value = "小米" // 5. 使用 brand.value 来修改响应式数据
}
function changePrice() {
console.log(price.value) // 6. 使用 price.value 来读取响应式数据
price.value += 50 // 7. 使用 price.value 来修改响应式数据
}
</script>
<style scoped>
.app {
background-color: aqua;
}
</style>
5. toRefs()
创建一个 ref 对象,其value值指向另一个对象中的某个属性值, 应用于将响应式对象中的某个属性单独提供给外部使用时
// 批量创建多个 ref 对象
const name = toRefs(person,"name")
代码示例
<template>
<div class="app">
<!-- 7. 使用 car.brand 来显示响应式的对象数据 -->
<h2>品牌: {{ car.brand }}</h2>
<h2>价格: {{ car.price }} 万</h2>
<button @click="changeBrand">修改品牌</button>
<button @click="changePrice">修改价格</button>
<button @click="changeObj">修改对象</button>
</div>
</template>
<script setup lang="ts" name="Person">
// 1. 引入 ref
import {reactive,toRefs} from "vue";
// 2. 使用 reactive 定义想要实现响应式的对象数据
let car = reactive({
brand: "奔驰",
price: 100
})
// 3. 将由 reactive 定义的每一组响应式数据取出, 使用 toRefs 来响应式的代理, 解构赋值取出响应式数据 brand、price
let {brand, price} = toRefs(car)
function changeBrand() {
brand.value = "小米" // 4. 使用 brand.value 来修改响应式数据
}
function changePrice() {
console.log(price.value) // 5. 使用 price.value 来读取响应式数据
price.value += 50 // 6. 使用 price.value 来修改响应式数据
}
</script>
<style scoped>
.app {
background-color: aqua;
}
</style>
3. 开发中如何使用
- 如果想定义一个基本类型的响应式数据, 必须使用
ref - 如果想定义一个对象类型的响应式数据, 并且层级不深,
ref、reactive都可以 - 如果想定义一个对象类型的响应式数据, 并且层级较深, 推荐使用
reactive
4. 响应式原理
1. ref() 响应式原理
1. 基本数据类型
ref函数对于基本数据类型使用的是数据劫持 getter setter 来实现的响应式
2. 复杂数据类型
ref函数对于复杂数据类型,求助了一个新函数--reactive函数,底层使用的是ES6中的 proxy 来实现的响应式
2. V2和V3 响应式对比
1. Vue2.x的响应式原理
- 对象类型: 通过Object.defineProperty()对属性的读取、修改进行拦截(数据劫持)
- 数组类型: 通过重写push等操作数组的一系列方法来实现拦截(对数组的操作方法进行了重写)
- 存在的问题:
- 新增属性、删除属性,模板不会自动更新
- 直接通过下表修改数组,模板不会自动更新
2. Vue3.x的响应式原理
- 实现原理
- 通过Proxy(代理): 拦截对象中任意属性的变化,包括: 属性值的读写、属性的添加、属性的删除等
- 通过Reflect(反射): 对被代理对象的属性进行操作
3. v2和v3的响应式实现对比
1. v2中的响应式
// 模拟vue2中实现响应式
let person = {
name:"小明",
age:18
}
let p = {}
Object.defineProperty(p,"name",{
configurable:true, // 定义可以删除某个属性
get(){ // 有人读取person中的name属性时调用
return person.name
},
set(value){ // 有人修改name时调用
person.name = value
}
})
Object.defineProperty(p,"age",{
get(){ // 有人读取person中的age属性时调用
return person.age
},
set(value){ // 有人修改age时调用
// 这里执行页面更新的逻辑
person.age = value
}
})
2. v3中的 Proxy (代理)
// 模拟vue3中实现响应式
let person = {
name: "小明",
age: 18
}
const p = new Proxy(person, {
get(target,propName) { // 当有人读取person身上的某个属性时调用,target就是person元对象,propName是操作的属性名
console.log(`有人读取了p身上的${propName}属性`)
return target[propName]
},
set(target,propName,value) { // 当有人修改或追加person身上的某个属性时调用,target就是person元对象,propName是操作的属性名
console.log(`有人修改了p身上的${propName}属性,值为${value},我要重新渲染页面了`)
target[propName] = value
},
deleteProperty(target, propName) { // 当有人删除person身上的某个属性时调用
console.log(`有人修改了p身上的${propName}属性,我要重新渲染页面了`)
return delete target[propName]
}
})
3. v3中的 Proxy + Reflect (反射)
// 模拟vue3中实现响应式
let person = {
name: "小明",
age: 18
}
const p = new Proxy(person, {
get(target,propName) {
console.log(`有人读取了p身上的${propName}属性`)
return Reflect.get(target,propName) // 调用反射来真正操作数据的增删改查,不会因为调用错误而直接导致线程的终止
},
set(target,propName,value) {
console.log(`有人修改了p身上的${propName}属性,值为${value},我要重新渲染页面了`)
return Reflect.set(target,propName,value)
},
deleteProperty(target, propName) {
console.log(`有人修改了p身上的${propName}属性,我要重新渲染页面了`)
return Reflect.deleteProperty(target,propName)
}
})
3. computed() --- 计算属性
1. 只读形式
<template>
<div class="app">
<!-- 4. 双向绑定 -->
<div>姓: <input type="text" v-model="person.firstName"></div>
<div>名: <input type="text" v-model="person.lastName"></div>
<br>
<!-- 5. 模板中使用 fullName 计算属性 -->
<span>全名: {{ fullName }}</span>
<button @click="changeFirstName">修改姓</button>
<button @click="changeLastName">修改名</button>
</div>
</template>
<script setup lang="ts" name="Person">
// 1. 引入 computed, reactive
import {computed, reactive} from "vue";
// 2. 使用 reactive 定义想要实现响应式的对象数据
let person = reactive({
firstName: "",
lastName: ""
})
// 3. 定义计算属性 fullName, 但是这样定义的计算属性是只读, 不允许修改的
let fullName = computed(() => {
return person.firstName + " " + person.lastName
})
function changeFirstName() {
person.firstName = "王";
}
function changeLastName() {
person.lastName = "二";
}
</script>
<style scoped>
.app {
background-color: aqua;
}
</style>
2. 可读可写
<template>
<div class="app">
<!-- 4. 双向绑定 -->
<div>姓: <input type="text" v-model="person.firstName"></div>
<div>名: <input type="text" v-model="person.lastName"></div>
<br>
<!-- 5. 模板中使用 fullName 计算属性 -->
<span>全名: {{ fullName }}</span>
<button @click="changeFirstName">修改姓</button>
<button @click="changeLastName">修改名</button>
<button @click="changeFullName">修改全名</button>
</div>
</template>
<script setup lang="ts" name="Person">
// 1. 引入 computed, reactive
import {computed, reactive} from "vue";
// 2. 使用 reactive 定义想要实现响应式的对象数据
let person = reactive({
firstName: "",
lastName: ""
})
// 3. 定义计算属性 fullName
let fullName = computed({
get() {
return person.firstName + "-" + person.lastName
},
set(value) {
const [firstName, lastName] = value.split("-")
person.firstName = firstName;
person.lastName = lastName;
}
})
function changeFirstName() {
person.firstName = "王";
}
function changeLastName() {
person.lastName = "二";
}
function changeFullName(){
fullName.value = "张-三" // 4. 使用 fullName.value 来修改 fullName
}
</script>
<style scoped>
.app {
background-color: aqua;
}
</style>
4. watch() --- 监视属性
Vue3 中的 watch() 只能监视以下四种数据:
ref定义的数据reactive定义的数据- 如果需要监视的属性值不是对象类型, 需要写成函数形式
- 一个包含上述内容的数组
1. 监视 ref 响应式数据
1. 监视一个基本类型
<template>
<div class="app">
<h3>{{ sum }}</h3>
<button @click="changeSum">点我 + 1</button>
</div>
</template>
<script setup lang="ts" name="Person">
// 1. 导入 ref watch
import {ref, watch} from "vue";
// 2. 定义 sum 响应式数据
let sum = ref(0)
function changeSum() {
sum.value += 1;
}
// 3. 监视 sum 响应式数据的变化
watch(sum, (newValue, oldValue) => {
console.log(newValue,oldValue)
})
</script>
<style scoped>
.app {
background-color: aqua;
}
</style>
2. 监视多个基本类型
<template>
<div class="app">
<h3>{{ sum }}</h3>
<button @click="changeSum">点我 + 1</button>
<br>
<h3>当前的信息是: {{ msg }}</h3>
<button @click="changeMsg">点我 + !</button>
</div>
</template>
<script setup lang="ts" name="Person">
// 1. 导入 ref watch
import {ref, watch} from "vue";
// 2. 定义 sum 和 msg 响应式数据
let sum = ref(0)
let msg = ref("天气真好啊")
function changeSum() {
sum.value += 1;
}
function changeMsg() {
msg.value += "!"
}
// 3. 同时监视 sum 和 msg 响应式数据的变化
watch([sum, msg], (newValue, oldValue) => {
console.log("sum的值变了", newValue[0], oldValue[0])
console.log("msg的值变了", newValue[1], oldValue[1])
})
</script>
<style scoped>
.app {
background-color: aqua;
}
</style>
3. 监视复杂类型对象
<template>
<div class="app">
<h2>姓名: {{person.name}}</h2>
<h2>年龄: {{person.age}}</h2>
<h2>薪水: {{person.job.salary}} 万元</h2>
<button @click="changeName">修改姓名</button>
<button @click="changeAge">增长年龄</button>
<button @click="changeSalary">涨薪</button>
<button @click="changePerson">修改人</button>
</div>
</template>
<script setup lang="ts" name="Person">
// 1. 导入 ref watch
import {ref, watch} from "vue";
// 2. 定义 person 响应式数据
let person = ref({
name: "张三",
age: 18,
job:{
salary:20
}
})
// 3. 修改整个 person 对象
function changePerson(){
person.value = {
name: "李四",
age: 20,
job:{
salary:10
}
}
}
// 4. 监视整个 person 对象的变化
watch(person, (newValue, oldValue) => {
console.log("person的值变了", newValue, oldValue)
})
</script>
<style scoped>
.app {
background-color: aqua;
}
</style>
4. 监视复杂类型对象 内部数据
1. 属性值不是对象类型
<template>
<div class="app">
<h2>姓名: {{person.name}}</h2>
<h2>年龄: {{person.age}}</h2>
<h2>薪水: {{person.job.salary}} 万</h2>
<button @click="changeName">修改姓名</button>
<button @click="changeAge">增长年龄</button>
<button @click="changePerson">修改人</button>
<button @click="changeSalary">涨薪</button>
</div>
</template>
<script setup lang="ts" name="Person">
// 1. 导入 ref watch
import {reactive, watch} from "vue";
// 2. 定义 person 响应式数据
let person = reactive({
name: "张三",
age: 18,
job:{
salary:20
}
})
// 3. 修改 person 对象内部的数据
function changeName(){
person.value.name = "小明"
}
function changeAge() {
person.value.age += 1
}
function changePerson(){
person.value = {
name: "李四",
age: 20,
}
}
// 4. 监视 person 对象 及 内部数据的变化, 需要手动开启深度监视
watch(person, (newValue, oldValue) => {
// 如果修改的是 person 内部的数据, `newValue` 此处和 `oldValue` 是相等的, 都是最新的值, 而监视不到旧的值, 因为它们是同一个对象!
// 如果修改的是 person 对象, `newValue` 是新的对象 `oldValue` 是旧的对象
console.log("person的值变了", newValue, oldValue)
},{deep:true})
// 5. 监听 person 对象内部的 name 的变化
watch(() => person.name, (newValue, oldValue) => {
console.log("person的值变了", newValue, oldValue)
})
// 6. 监听 person 对象内部的 obj 中 salary 的变化
watch(()=>person.job.salary, (newValue, oldValue) => {
console.log("person的值变了", newValue, oldValue)
},{deep:true})
</script>
<style scoped>
.app {
background-color: aqua;
}
</style>
2. 属性值是对象类型
<template>
<div class="app">
<h2>姓名: {{person.name}}</h2>
<h2>年龄: {{person.age}}</h2>
<h2>薪水: {{person.job.salary}} 万</h2>
<button @click="changeName">修改姓名</button>
<button @click="changeAge">增长年龄</button>
<button @click="changePerson">修改人</button>
<button @click="changeSalary">涨薪</button>
<button @click="changeJob">修改工作</button>
</div>
</template>
<script setup lang="ts" name="Person">
// 1. 导入 ref watch
import {reactive, watch} from "vue";
// 2. 定义 person 响应式数据
let person = reactive({
name: "张三",
age: 18,
job: {
salary: 20
}
})
// 3. 修改 person 对象内部的数据
function changeName() {
person.name = "小明"
}
function changeAge() {
person.age += 1
}
function changePerson() {
person.job = {
salary: 25
}
}
function changeSalary() {
person.job.salary += 1
}
function changeJob() {
person.job = {
salary: 25
}
}
// 4. 监听 [reactive] 定义的 [对象类型] 数据内部的 job 的变化, 默认是开启深度监视的, 且无法被关闭
watch(() => person.job, (newValue, oldValue) => {
console.log("person的值变了", newValue, oldValue)
})
// 5. 监听 [reactive] 定义的 [对象类型] 数据内部的 job 及其内部 的变化, 需要手动再次开启深度监视
watch(() => person.job, (newValue, oldValue) => {
console.log("person的值变了", newValue, oldValue)
},{deep: true})
</script>
<style scoped>
.app {
background-color: aqua;
}
</style>
5. 监视多个复杂类型对象
<template>
<div class="app">
<h2>姓名: {{person.name}}</h2>
<h2>年龄: {{person.age}}</h2>
<h2>薪水: {{person.job.salary}} 万</h2>
<button @click="changeName">修改姓名</button>
<button @click="changeAge">增长年龄</button>
<button @click="changePerson">修改人</button>
<button @click="changeSalary">涨薪</button>
</div>
</template>
<script setup lang="ts" name="Person">
// 1. 导入 ref watch
import {reactive, watch} from "vue";
// 2. 定义 person 响应式数据
let person = reactive({
name: "张三",
age: 18,
job: {
salary: 20
}
})
// 3. 修改 person 对象内部的数据
function changeName() {
person.name = "小明"
}
function changeAge() {
person.age += 1
}
function changePerson() {
Object.assign(person, {
name: "李四",
age: 20,
})
}
function changeSalary() {
person.job.salary += 1
}
// 6. 监听 多个 [reactive] 定义的 [对象类型] 数据内部数据, 默认是开启深度监视的, 且无法被关闭
watch([() => person.job.salary, () => person.name, () => person.age], (newValue, oldValue) => {
console.log("person的值变了", newValue, oldValue)
})
</script>
<style scoped>
.app {
background-color: aqua;
}
</style>
6. 总结
如果监视的是对象中的属性值, 直接用函数形式 ( () => person.name ), 如果监视的是嵌套对象, 默认是不监视嵌套对象内部属性值的变化, 如果想见识, 需要手动添加配置开启深度监视 { deep: true}
2. 监视 reactive 响应式数据
<template>
<div class="app">
<h2>姓名: {{person.name}}</h2>
<h2>年龄: {{person.age}}</h2>
<button @click="changeName">修改姓名</button>
<button @click="changeAge">增长年龄</button>
<button @click="changePerson">修改人</button>
</div>
</template>
<script setup lang="ts" name="Person">
// 1. 导入 ref watch
import {reactive, watch} from "vue";
// 2. 定义 person 响应式数据
let person = reactive({
name: "张三",
age: 18,
job: {
salary: 20
}
})
// 3. 修改 person 对象内部的数据
function changeName() {
person.name = "小明"
}
function changeAge() {
person.age += 1
}
function changePerson() {
Object.assign(person, {
name: "李四",
age: 20,
})
}
// 4. 监视 [reactive] 定义的 [对象类型] 数据, 默认是开启深度监视的, 且无法被关闭
watch(person, (newValue, oldValue) => {
// 如果修改的是 person 内部的数据, `newValue` 此处和 `oldValue` 是相等的, 都是最新的值, 而监视不到旧的值, 因为它们是同一个对象(地址没发生变化)!
// 如果修改的是 person 对象, `newValue` 是新的对象 `oldValue` 是旧的对象
console.log("person的值变了", newValue, oldValue)
}, {deep: true})
// 5. 监听 [reactive] 定义的 [对象类型] 数据内部的 name 的变化, 默认是开启深度监视的, 且无法被关闭
watch(() => person.name, (newValue, oldValue) => {
console.log("person的值变了", newValue, oldValue)
})
</script>
<style scoped>
.app {
background-color: aqua;
}
</style>
3. 深度监视
对于需要监视 ref 定义的 复杂数据类型 及 其内部数据 的变化, 需要手动开启深度监视
<template>
<div class="app">
<h2>姓名: {{person.name}}</h2>
<h2>年龄: {{person.age}}</h2>
<h2>薪水: {{person.job.salary}} 万元</h2>
<button @click="changeName">修改姓名</button>
<button @click="changeAge">增长年龄</button>
<button @click="changeSalary">涨薪</button>
<button @click="changePerson">修改人</button>
</div>
</template>
<script setup lang="ts" name="Person">
// 1. 导入 ref watch
import {ref, watch} from "vue";
// 2. 定义 person 响应式数据
let person = ref({
name: "张三",
age: 18,
job:{
salary:20
}
})
function changeName(){
person.value.name = "小明"
}
function changeAge() {
person.value.age += 1
}
function changeSalary() {
person.value.job.salary += 1
}
function changePerson(){
person.value = {
name: "李四",
age: 20,
job:{
salary:10
}
}
}
// 3. 监视 person 对象 及 内部数据的变化, 需要手动开启深度监视
watch(person, (newValue, oldValue) => {
console.log("person的值变了", newValue, oldValue)
},{deep:true})
</script>
<style scoped>
.app {
background-color: aqua;
}
</style>
对于 reactive 定义的 复杂数据类型 及 其内部数据 的变化, 默认开启深度监视, 且无法关闭
<template>
<div class="app">
<h2>姓名: {{person.name}}</h2>
<h2>年龄: {{person.age}}</h2>
<h2>薪水: {{person.job.salary}} 万</h2>
<button @click="changeName">修改姓名</button>
<button @click="changeAge">增长年龄</button>
<button @click="changePerson">修改人</button>
<button @click="changeSalary">涨薪</button>
</div>
</template>
<script setup lang="ts" name="Person">
// 1. 导入 ref watch
import {reactive, watch} from "vue";
// 2. 定义 person 响应式数据
let person = reactive({
name: "张三",
age: 18,
job: {
salary: 20
}
})
// 3. 修改 person 对象内部的数据
function changeName() {
person.name = "小明"
}
function changeAge() {
person.age += 1
}
function changePerson() {
Object.assign(person, {
name: "李四",
age: 20,
})
}
function changeSalary(){
person.job.salary += 1
}
// 4. 监视 [reactive] 定义的 [对象类型] 数据, 默认是开启深度监视的, 且无法被关闭
watch(person, (newValue, oldValue) => {
// 如果修改的是 person 内部的数据, `newValue` 此处和 `oldValue` 是相等的, 都是最新的值, 而监视不到旧的值, 因为它们是同一个对象(地址没发生变化)!
// 如果修改的是 person 对象, `newValue` 是新的对象 `oldValue` 是旧的对象
console.log("person的值变了", newValue, oldValue)
}, {deep: true})
// 5. 监听 [reactive] 定义的 [对象类型] 数据内部的 name 的变化, 默认是开启深度监视的, 且无法被关闭
watch(() => person.name, (newValue, oldValue) => {
console.log("person的值变了", newValue, oldValue)
})
// 6. 监听 [reactive] 定义的 [对象类型] 数据内部的 job 中的 salary 的变化, 默认是开启深度监视的, 且无法被关闭
watch(() => person.job.salary, (newValue, oldValue) => {
console.log("person的值变了", newValue, oldValue)
})
</script>
<style scoped>
.app {
background-color: aqua;
}
</style>
3. 首次监视
首次变化是指当页面渲染的时候,触发一次监视, 及其之后的每次变化
<template>
<div class="app">
<h2>姓名: {{person.name}}</h2>
<h2>年龄: {{person.age}}</h2>
<h2>薪水: {{person.job.salary}} 万元</h2>
<button @click="changeName">修改姓名</button>
<button @click="changeAge">增长年龄</button>
<button @click="changeSalary">涨薪</button>
<button @click="changePerson">修改人</button>
</div>
</template>
<script setup lang="ts" name="Person">
// 1. 导入 ref watch
import {ref, watch} from "vue";
// 2. 定义 person 响应式数据
let person = ref({
name: "张三",
age: 18,
job:{
salary:20
}
})
function changeName(){
person.value.name = "小明"
}
function changeAge() {
person.value.age += 1
}
function changeSalary() {
person.value.job.salary += 1
}
function changePerson(){
person.value = {
name: "李四",
age: 20,
job:{
salary:10
}
}
}
// 3. 监视整个 person 对象的首次变化及以后得每次变化
watch(person, (newValue, oldValue) => {
console.log("person的值变了", newValue, oldValue)
},{immediate:true})
</script>
<style scoped>
.app {
background-color: aqua;
}
</style>
4. 停止监视
<template>
<div class="app">
<h3>{{ sum }}</h3>
<button @click="changeSum">点我 + 1</button>
</div>
</template>
<script setup lang="ts" name="Person">
// 1. 导入 ref watch
import {ref, watch} from "vue";
// 2. 定义 sum 响应式数据
let sum = ref(0)
function changeSum() {
sum.value += 1;
}
// 3. 监视 sum 响应式数据的变化
const stopWatch = watch(sum, (newValue, oldValue) => {
console.log(newValue, oldValue)
if (newValue === 10){ // 当 sum 的值为 10 时, 停止监视
stopWatch()
}
})
</script>
<style scoped>
.app {
background-color: aqua;
}
</style>
6. watchEffect()
- watch的写法是: 既要指明监视的属性,也要指明监视的回调
- watchEffect的写法是: 不用指明监视哪个属性, 回调函数中用到哪个属性,监视的就是哪个属性
- watchEffect有点像computed的执行流程:
- 但computed注重的计算出来的值(回调函数的返回值),所以必须要写返回值
- 而watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值
**需求: ** 当 水温达到 6℃ 或者 水位到达 40ml 时, 发送一个请求
<template>
<div class="app">
<h2>当前水位: {{wHeight}}ml</h2>
<h2>当前水温: {{temp}}℃</h2>
<button @click="changeWheight">水位+10</button>
<button @click="changeTemp">水温+2</button>
</div>
</template>
<script setup lang="ts" name="Person">
import {ref, watch, watchEffect} from 'vue'
let temp = ref(0)
let wHeight = ref(0)
function changeTemp() {
temp.value += 2
}
function changeWheight() {
wHeight.value += 10
}
// watch --- 必须显式的定义, 监视多个值的变化
watch([temp, wHeight], function (value) {
let [newTemp, newWheight] = value
if (newTemp >= 6 || newWheight >= 40) {
console.log("发送了请求")
}
})
// watchEffect --- 会自动监视逻辑中用到的数据的变化, 并且会首次执行一次
watchEffect(() => {
if (temp.value >= 6 || wHeight.value >= 40){
console.log("发送了请求")
}
})
</script>
<style scoped>
.app {
background-color: aqua;
}
</style>
5. ref 属性
1. 加在 HTML 标签上
需求: 如何在组件中取出 DOM 元素
<template>
<div class="app">
<!-- 1. 给标签绑定 id 属性 -->
<h2 id="temp">当前水位</h2>
<h2>当前水温</h2>
<button @click="showDom">获取 DOM 元素</button>
</div>
</template>
<script setup lang="ts" name="Person">
function showDom(){
// 2. 获取到 id="temp" 的 DOM 元素
console.log(document.getElementById('temp'))
}
</script>
<style scoped>
.app {
background-color: aqua;
}
</style>
1. 假如其他页面中也有 id='temp' 的标签, 当页面全部渲染成功后, 就会冲突, 此时可以使用 ref 属性 来标记定义
<template>
<div class="app">
<!-- 1. 使用 ref属性, 打标记 -->
<h2 ref="temp">当前水位</h2>
<h2>当前水温</h2>
<button @click="showDom">获取 DOM 元素</button>
</div>
</template>
<script setup lang="ts" name="Person">
import {ref} from 'vue'
// 2. 声明 temp 来存储 ref 所标记的内容
let temp = ref()
function showDom(){
// 3. 使用 temp.value 来获取 DOM 元素
console.log(temp.value)
}
</script>
<style scoped>
.app {
background-color: aqua;
}
</style>
2. 加在 组件 标签上 ( 子向父传值)
APP.vue
<template>
<!-- 1. 用 ref 在组件上面做标记, 得到的是 组件实例 -->
<Person ref="ren"/>
<button @click="showRefMessage">输出 ref 自定义数据</button>
</template>
<script lang="ts" setup name="App">
import Person from './components/Person.vue'
import {ref} from 'vue'
let ren = ref()
function showRefMessage() {
// 得到的是 组件实例
console.log(ren.value)
// 获取子组件中的数据
console.log(ren.value.a, ren.value.b, ren.value.c)
}
</script>
Person.vue
<template>
<div class="app">
<h2 ref="temp">当前水位</h2>
<h2>当前水温</h2>
<button @click="showDom">获取 DOM 元素</button>
<button @click="showABC">获取 abc 的值</button>
</div>
</template>
<script setup lang="ts" name="Person">
import {defineExpose, ref} from 'vue' // defineExpose 可以不引入, vue 内部已经缓存了这个函数
let temp = ref()
let a = ref(0)
let b = ref(1)
let c = ref(2)
function showDom() {
console.log(temp.value)
}
function showABC() {
console.log(a.value, b.value, c.value)
}
// 指定向父组件暴露的数据
defineExpose({a, b, c})
</script>
<style scoped>
.app {
background-color: aqua;
}
</style>
6. Vue 中对 TS 的应用
1. 接口
src/types/index.ts
export interface Person {
id: string
name: string;
age: number;
}
src/components/Person.vue
<template>
<div class="app">
</div>
</template>
<script setup lang="ts" name="Person">
import {type Person} from '@/types'
// 限制 personList 符合 Person 接口定义的规范
let person: Person = {
id: "asdfasdgasd",
name: "小明",
age: 20
}
// 限制 personList 是一个数组, 并且其中的每一项都符合 Person 接口定义的规范
let personList: Array<Person> = [
{id: "asdfasdgasd01", name: "小明", age: 20},
{id: "asdfasdgasd02", name: "小红", age: 15},
{id: "asdfasdgasd03", name: "小白", age: 10}
]
</script>
<style scoped>
.app {
background-color: aqua;
}
</style>
上面数组类型的简写形式
src/type/index.ts
export interface Person {
id: string
name: string;
age: number;
x?: number; // x? 表示 x 这个参数不是必填项
}
// 定义符合 Person 接口规范的数组类型
export type PersonList = Array<Person>; // 写法一
export type PersonList = Person[]; // 写法二
src/components/Person.vue
<template>
<div class="app">
</div>
</template>
<script setup lang="ts" name="Person">
import {reactive} from "vue";
import {type PersonList} from '@/types'
// 限制 personList 是一个数组, 并且其中的每一项都符合 Person 接口定义的规范, 并使用 reactive 将其变成响应式数据
let personList = reactive<PersonList>([
{id: "asdfasdgasd01", name: "小明", age: 20},
{id: "asdfasdgasd02", name: "小红", age: 15},
{id: "asdfasdgasd03", name: "小白", age: 10}
])
</script>
<style scoped>
.app {
background-color: aqua;
}
</style>
7. 生命周期对比
0. 对比图


1. 配置项式 生命周期
App.vue
<template>
<button @click="isShow = !isShow">显示/隐藏</button>
<Home v-if="isShow"/>
</template>
<script>
import {ref} from 'vue'
import Home from "@/components/Home";
export default {
name: 'App',
// eslint-disable-next-line vue/no-unused-components
components:{Home},
setup(){
let isShow = ref(true)
return {
isShow
}
}
}
</script>
<style>
</style>
Home.vue
<template>
<h3>名字: {{person.name}}</h3>
<h3>年龄: {{person.age}}</h3>
<h3>薪水: {{person.job.salary}}</h3>
<button @click="person.job.salary++">点我涨薪</button>
</template>
<script>
import {reactive, ref} from "vue";
export default {
// eslint-disable-next-line vue/multi-word-component-names
name: "Home",
setup() {
let sum = ref(0)
let person = reactive({
name: "小明",
age: 18,
job: {
salary: 20
}
})
return {
sum,
person,
}
},
// 创建前
beforeCreate() {
console.log("----beforeCreate----")
},
// 创建完毕
created() {
console.log("----created----")
},
// 挂载前
beforeMount() {
console.log("----beforeMount----")
},
// 挂载完毕
mounted() {
console.log("----mounted----")
},
// 更新前
beforeUpdate() {
console.log("----beforeUpdate----")
},
// 更新完毕
updated() {
console.log("----updated----")
},
// 卸载前
beforeUnmount() {
console.log("----beforeUnmount----")
},
// 卸载完毕
unmounted() {
console.log("----unmounted----")
},
}
</script>
<style scoped>
</style>
2. 组合式 生命周期
- Vue3中可以继续使用Vue2中以配置项的形式定义生命周期钩子,但有两个钩子更名了:
beforeDestroy改名为beforeUnmountdestroyed改名为unmounted
- Vue3 也提供了 Compositon API 形式的生命周期钩子,与配置项形式的对应关系为:
- beforeCreate --- setup()
- created ----------- setup()
- beforeMount ----- onBeforeMount()
- mounted ---------- onMounted()
- beforeUpdate --- onBeforeUpdate()
- updated ----------- onUpdated()
- beforeUnmount -- onBeforeUnmount()
- unmounted -------- onUnmounted()
<template>
<h3>名字: {{person.name}}</h3>
<h3>年龄: {{person.age}}</h3>
<h3>薪水: {{person.job.salary}}</h3>
<button @click="person.job.salary++">点我涨薪</button>
</template>
<script setup lang="ts" name="Person">
import {
onBeforeMount,
onBeforeUnmount,
onBeforeUpdate,
onMounted,
onUnmounted,
onUpdated,
reactive,
ref
} from "vue";
let sum = ref(0)
let person = reactive({
name: "小明",
age: 18,
job: {
salary: 20
}
})
// 挂载前
onBeforeMount(() => {
console.log("---- onBeforeMount ----")
})
// 挂载完毕
onMounted(() => {
console.log("---- onMounted ----")
})
// 更新前
onBeforeUpdate(() => {
console.log("---- onBeforeUpdate ----")
})
// 更新完毕
onUpdated(() => {
console.log("---- onUpdated ----")
})
// 卸载前
onBeforeUnmount(() => {
console.log("---- onBeforeUnmount ----")
})
// 卸载完毕
onUnmounted(() => {
console.log("---- onUnmounted ----")
})
</script>
<style scoped>
</style>
3. 嵌套组件 生命周期
子组件的所有生命周期执行完毕, 再执行父组件的所有生命周期
8. hooks ( 必写 )
本质是一个函数,把setup函数中使用到的的一系列的Coposition API 封装成一个函数,然后在页面中使用 类似于vue2中的mixin
自定义hook的优势:
- 复用代码,让setup中的逻辑更清楚易懂
src/hooks/usePoint.js 名字一般为 useXxx
import {onBeforeUnmount, onMounted, reactive, computed} from "vue";
// 1. 实现鼠标坐标展示的功能
export default function usePoint() {
let point = reactive({
x: 0,
y: 0
})
function savePoint(event) {
point.x = event.pageX
point.y = event.pageY
console.log(event.pageX, event.pageY)
}
// hook 函数中可以定义钩子函数
onMounted(() => {
window.addEventListener('click', savePoint)
})
onBeforeUnmount(() => {
window.removeEventListener('click', savePoint)
})
// hook 函数中可以定义计算属性
let zPoint = computed(()=>{
return point.x * 10
})
// 2. 必须 return
return {point, savePoint, zPoint}
}
Home.vue
<template>
<h2>当前点击时鼠标的坐标</h2>
<!-- 3. 模版中使用 -->
<span> x: {{point.x}}</span>
<br>
<span> y: {{point.y}}</span>
</template>
<script lang="ts" setup name="Home">
// 1. 导入hook
import usePoint from '@/hooks/use_point'
// 2. 解构 hooks 中的数据
let {point} = usePoint()
</script>
<style scoped>
</style>
9. 其他常用组合式 API
1. 浅层响应式加工函数
1. shalloReactive()
shalloReactive: 只处理对象最外层属性的响应式(浅响应式)
2. shallowRef()
shallowRef: 只处理基本数据类型的响应式,不进行对象的响应式处理
3. 应用场景
- 如果有一个对象数据,结构比较深,但变化时只是外层属性变化使用 shalloReactive
- 如果有一个对象数据,后续功能不会修改该对象中的属性,而是生成新的对象来替换使用 shallowRef
2. 深浅只读
1. readonly
readonly: 让一个响应式数据变为只读的(深只读)
2. shallowReadonly
shallowReadonly: 让一个响应式数据第一层变为只读的(浅只读)
3. 应用场景
应用场景: 不希望数据被修改时
3. 转成非响应式数据
1. toRaw
toRaw : 将一个由 reactive 生成的响应式对象转为普通对象
2. markRaw
markRaw:标记一个对象,使其永远不会再成为响应式对象
3. 应用场景
toRaw : 用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新
markRaw:
- 有些值不应被设置为响应式的,例如: 复杂的第三方类库等
- 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能
4. 自定义响应式数据
1. customRef()
作用: 创建一个自定义的ref,并对其依赖项跟踪和更新触发进行显式控制
2. 案例: 防抖
案例: 实现防抖(延迟显示)效果
<template>
<input type="text" v-model="keyword">
<h3>{{ keyword }}</h3>
</template>
<script lang="ts" setup name="Index">
import {customRef,ref} from "vue";
// 自定义一个myRef
function myRef(value:string, delay:number) {
let timer:number
// 通过customRef去实现自定义
return customRef((track, trigger) => {
return {
get() {
track() // 通知 vue 持续追踪 value 的变化,让 vue 知道 value 是有用的, 一旦数据有所变化, 就要去更新
return value
},
set(newValue) {
clearTimeout(timer)
timer = setTimeout(() => {
value = newValue // 将原数据修改为最新数据
trigger() // 通知 vue 数据已经变了, 重新解析模版
}, delay)
}
}
})
}
// let keyword = ref("hello") // 使用Vue内置的ref, 无法实现防抖效果
let keyword = myRef("hello", 500) // 使用自定义的 myRef 来加工响应式数据, 可以使用逻辑来自定义控制响应式数据
</script>
<style>
</style>
5. 响应式数据的判断
isRef: 检查一个值是不是一个 ref 对象
isReactive: 检查一个对象是否是由 reactive 创建的响应式代理
isReadonly: 检查一个对象是否是由 readonly 创建的只读代理
isProxy: 检查一个对象是否是由 reactive 或者 readonly方法创建的代理
10. 新的组件
1. Fragment ( 虚拟标签 )
在Vue2 中组件必须有一个根标签,在Vue3中组件可以没有根标签,内部会将多个标签包含在一个Fragment组件(虚拟元素)中
目的: 减少标签层级、减小内存占用
2. Teleport ( 将组件移动至指定位置 )
Teleport 是一种能够让 组件HTML结构 移动到指定位置的技术
模态对话框案例
Son.vue
<template>
<div class="son">
<Dialog />
</div>
</template>
<script lang="ts" setup name="Son">
import Dialog from './Dialog.vue';
</script>
<style scoped>
</style>
Dilog.vue
<template>
<div>
<button @click="isShow = true">弹窗</button>
<!-- 将以下的标签,传送到body中,只针对body的位置变化而变化,其他组件怎么变都不会影响到模态对话框 -->
<teleport to="body">
<div class="mask" v-if="isShow">
<div class="dialog">
<h3>我是弹窗</h3>
<div>账号: <input type="text"></div>
<div>密码: <input type="password"></div>
<button @click="isShow = false">关闭</button>
</div>
</div>
</teleport>
</div>
</template>
<script lang="ts" setup name="Dialog">
import {ref} from "vue";
let isShow = ref(false)
</script>
<style scoped>
.mask {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
background-color: rgba(0,0,0,0.5);
}
.dialog {
text-align: center;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-75%,-75%);
width: 300px;
height: 300px;
background-color: green;
}
</style>
3. Suspense ( 标识为异步组件 )
等待异步组件时,渲染一些额外内容,让应用有更好的用户体验
**应用场景: ** 子组件中需要执行异步任务的时候, 由于使用了 setup 语法糖, 底层会自动加 async, 但是这样会导致无法正常显示子组件的内容, 此时就需要在父组件中, 用 Suspense 标识子组件是异步组件
1. 基本使用
src/components/Father.vue
<template>
<h2>我是App</h2>
<!-- 1. 父组件中使用 Suspense 来表示子组件是一个异步组件 -->
<Suspense>
<!-- 2. Suspense 底层是使用插槽实现的 -->
<template #default>
<Son />
</template>
</Suspense>
</template>
<script lang="ts" setup name="Father">
import Son from './components/Son.vue';
</script>
<style>
</style>
src/components/Son.vue
<template>
<h2>我是Home</h2>
</template>
<script lang="ts" setup name="Son">
import axios from 'axios'
// 发送网络请求
const res = await axios.get("https://api.uomg.com/api/rand.qinghua?format=json")
console.log(res.data);
</script>
<style scoped>
</style>
2. 解决子组件闪现问题
在网络请求中, 由于有延迟, 子组件会突然出现在页面中, 用户体验不好
src/components/Father.vue
<template>
<h2>我是App</h2>
<!-- 1. 父组件中使用 Suspense 来表示子组件是一个异步组件 -->
<Suspense>
<!-- 2. 异步任务有结果的时候, 出现 default 中的内容 -->
<template #default>
<Home/>
</template>
<!-- 3. 异步任务没有结果的时候, 默认出现 fallback 中的内容 -->
<template #fallback>
<h2>加载中....</h2>
</template>
</Suspense>
</template>
<script lang="ts" setup name="App">
import Home from './components/Home.vue';
</script>
<style>
</style>
子组件无需变动
3. V2 中的使用步骤
src/components/Father.vue
<template>
<h2>我是App</h2>
<Home/>
</template>
<script>
// 静态引入方式
// import Home from "@/components/Home";
// 异步引入方式
import {defineAsyncComponent} from 'vue'
const Home = defineAsyncComponent(() => import('@/components/Home'))
export default {
name: 'App',
components: {Home},
}
</script>
<style>
</style>
1. 异步引入组件
<template>
<h2>我是App</h2>
<Home/>
</template>
<script>
// 静态引入方式
// import Home from "@/components/Home";
// 异步引入方式
import {defineAsyncComponent} from 'vue'
const Home = defineAsyncComponent(() => import('@/components/Home'))
export default {
name: 'App',
components: {Home},
}
</script>
<style>
</style>
2. 使用 Suspense 组件解决组件闪现问题
<template>
<h2>我是App</h2>
<Suspense>
<!-- 渲染完成展示的内容,插槽名必须是default -->
<template v-slot:default>
<Home/>
</template>
<!-- Home组件还没渲染完成之前默认显示的内容,插槽名必须是fallback -->
<template v-slot:fallback>
<h2>加载中....</h2>
</template>
</Suspense>
</template>
<script>
// 异步引入方式
import {defineAsyncComponent} from 'vue'
const Home = defineAsyncComponent(() => import('@/components/Home'))
export default {
name: 'App',
components: {Home},
setup() {
}
}
</script>
<style>
</style>
Home.vue 中调用异步任务
<template>
<h2>我是Home</h2>
</template>
<script>
import {ref} from 'vue'
export default {
name: "Home",
setup() {
let sum = ref(0)
return new Promise((resolve, reject) => {
setTimeout(()=>{
console.log(reject)
resolve({sum})
},3000)
})
},
// 或者将setup函数变为异步
async setup() {
let sum = ref(0)
let p = new Promise((resolve, reject) => {
setTimeout(() => {
console.log(reject)
resolve({sum})
}, 3000)
})
return await p
},
}
</script>
<style scoped>
</style>
11. 全局 API 的转移
1. Vue2中有很多全局API和配置
// 例如: 注册全局组件
Vue.component("myButton",{
data: ()=>{
count: 0
},
template: '<button @click="count++">Clicked {{count}} times.</button>'
})
// 注册全局指令等
Vue.directive('focus',{
inserted: el => el.focus()
})
2. Vue3中对这些API做出了调整
将全局API,即 Vue.xxx 调整到应用实例 app 上
| Vue2 全局 API (Vue.xxx) | Vue3 实例API (app.xxx) |
|---|---|
| Vue.config.xxx | app.config.xxx |
| Vue.config.productionTip | 移除 |
| Vue.component | app.component |
| Vue.directive | app.directive |
| Vue.mixin | app.mixin |
| Vue.use | app.use |
| Vue.prototype | app.config.globalProperties |
12. 非兼容改变
1. data选项
始终被声明为一个函数
2. 过度类名的更改
1. Vue2的写法
.v-enter,
.v-leave-to{
opacity:0
}
.v-leave,
.v-enter-to{
opactiy:1
}
2. Vue3的写法
.v-enter-from,
.v-leave-to{
opacity:0
}
.v-leave-from,
.v-enter-to{
opactiy:1
}
3. v-if 和 v-for 在同一个元素的优先级与 V2 相反
V2 中 v-if 和 v-for 是不允许同时出现在同一个标签上的, 因为 v-for 的优先级要比 v-if 高
V3 中 允许同时出现在同一个标签上, 修改为 v-if 的优先级高于 v-for
4.移除 v-on 的 keyCode 修饰符
移除 keyCode 作为 v-on 的修饰符,同时也不再支持config-keyCodes,因为兼容性差
v-on:enter=""
v-on:13="" <!-- 被移除 -->
5. 移除 v-on 的 nactive 修饰符
移除 v-on.nactive 修饰符,用来修饰为原生事件
1. 父组件中绑定事件
<Home
v-on:close="handleComponentEvent"
v-on:click="handleNativeClickEvent"
/>
2. 子组件中声明自定义事件
export default {
emits: ['close'] // 指定自定义事件,未声明的都是原生事件
}
6. 移除 v-bind.sync 修饰符, 将其整合到了 v-model 中
7. 移除 $on $off $once 实例方法
8. 移除过滤器 ( filter )
移除 过滤器(filter) 虽然看起来很方便,但它需要一个自定义语法,虚席成本和实现成本高,建议用方法调用或计算属性替换过滤器

浙公网安备 33010602011771号