Options API的弊端
Options API将一个功能的数据、方法、计算属性分散在data、methods、computed等配置项中,如果想新增或者修改一个需求,就需要分别对data、methods、computed进行分别修改,不利于复用与维护。
Composition API的优势
可以用函数的方式,更加优雅的组织代码,让一个功能相关的代码更加有序集中地组织在一块区域。
拉开序幕的setup
- 理解:setup是Vue3新增的一个配置项,它的值是一个函数。
- setup是所有Composition API"表演的舞台"。
- 组件中所用到的:数据、方法等等,均要在setup中配置。
setup函数的两种返回值
- 如果返回一个对象,则对象的属性、方法在模板中均可以直接使用
- 如果返回一个渲染函数:则可以自定义渲染内容
<template>
<div>
姓名:{{ name }}
年龄:{{ age }}
</div>
</template>
<script>
import { h } from 'vue'
export default {
setup() {
// 1.返回一个对象
return {
name: '兰青',
age: 23
}
// 2.返回一个渲染函数
// return () => h('div', '兰艺')
}
}
</script>
<style scoped></style>
注意事项
- 尽量不要与Vue2混用。
- Vue2可以访问setuo函数中的属性和方法
- setup不能访Vue2的配置
- 如果有重名,setup优先
2.setup不能是一个async函数,因为返回值不再是return对象,而是promise,模板看不到return对象中的属性。(后期也可以返回一个Promise实例,但是需要Suspense和异步组件的配合)
ref
<template>
<div>
姓名:{{ name }}
年龄:{{ age }}
<button @click="changeInfo">改变信息</button>
</div>
</template>
<script>
import { ref } from 'vue'
export default {
setup() {
// 1.返回一个对象
let name = ref('兰青')
let age = ref(18)
console.log('name', name);
console.log('age', age);
let changeInfo = () => {
console.log('changeInfo');
name.value += '1'
age.value += 1
}
return {
name,
age,
changeInfo
}
}
}
</script>
<style scoped></style>
输出结果
解读一下名词:
RefImpl:Ref的意思是reference(引用) Impl的意思是Implement(实现),合起来为引用实现的实例对象(简称:引用对象)
ref总结
作用:定义一个响应式的数据
语法:const xxx=ref(initValue)
- 创建一个包含响应式数据的
引用对象(reference对象) - JS中操作数据:xxx.value
- 模板中读取数据:不需要.value
备注:
- 接收的数据可以是基本类型也可以是对象类型
- 基本类型的数据:响应式依然是靠
Object.defineProperty()中的get与set完成的 - 对象类型的数据:内部“求助”了Vue3.0中的一个新函数-
reactive函数
reactive
作用:定义一个对象类型的响应式数据(基本类型不要使用它)
语法: const 代理对象=reactive(源对象)接收一个对象(或数组),返回一个代理对象(Proxy的实例对象,简称proxy对象)
reactive定义的响应式数据是深层次的
内部基于ES6的proxy实现,通过代理对象操作源对象内部数据进行操作
Vue3响应式
实现原理:
- 通过Proxy(代理):拦截对象中任意属性的变化,包括:属性值的增删改查
- 通过Reflect(反射):对源对象的属性进行操作
<body>
<script>
let person = {
name: '兰青',
age: 23
}
//模拟Vue3响应式
let 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)
}
})
</script>
</body>
reactive对比ref
1.从定义数据角度对比:
ref用来定义:基本数据类型
reactive用来定义:对象(或数组)类型数据
注意:ref也可以用来定义数组或者对象,但是其内部也是通过reactive转为代理对象
2.从原理角度对比:
ref通过Object.defineProperty()的get与set来实现响应式(数据劫持)
reactive通过使用Proxy来实现响应式(数据劫持),并且通过Reflect操作源对象内部的数据。(Reflect相关内容不是很会)
3.从使用角度对比:
ref定义的数据:操作数据需要.value,读取数据时模板直接读取不需要.value
setup两个注意点
1.setup执行时机:在beforeCreate之前执行一次,this是undefined
2.setup的参数
props:值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性
context:上下文对象
-
attrs:值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性,相当于this.$attrs -
slots:收到的插槽内容,相当于this.$slots -
emit:分发自定义事件的函数,相当于this.$emit
计算属性
<template>
<div>
name:{{ name }}
lastName:{{ lastName }}
fullName:{{ fullName }}
<button @click="add">+</button>
</div>
</template>
<script>
import { ref, reactive, computed } from 'vue'
export default {
props: ['msg', 'sex'],
setup() {
let name = ref('John');
let lastName = ref('Doe');
// 简写方式
let fullName = computed(() => {
return `${name.value} ${lastName.value}`;
})
//全写方式
let fullName = computed({
// get方法负责返回计算结果
get() {
return `${name.value} ${lastName.value}`;
},
// set方法则在外部试图更改计算属性时被调用,用于更新依赖的响应式状态
set(newValue) {
console.log('set 方法newValue', newValue);
const [firstName, lastName] = newValue.split(' ');
console.log('firstName', firstName);
console.log('lastName', lastName);
name.value = firstName;
lastName.value = lastName;
},
});
const add = () => {
console.log('add');
fullName.value = '111 11'
}
return {
name,
lastName,
fullName,
add
};
},
}
</script>
<style scoped></style>
watch属性
watch监听ref定义的数据
1.监听单个数据
2.监听多个数据
<template>
<div>
{{ a }} <br />
{{ b }}
</div>
<button @click="add">+</button>
</template>
<script>
import { ref, watch } from 'vue'
export default {
setup() {
let a = ref(0)
let b = ref(0)
const add = () => {
a.value++
b.value++
}
// 监听一个数据
// watch(a, (newValue) => {
// console.log('newValue', newValue); // 1
// }, { immediate: true })
// 监听多个数据
watch([a, b], (newValue) => {
console.log('newValue', newValue); // [1,1]
}, { immediate: true })
return {
a,
b,
add
};
},
}
</script>
<style scoped></style>
watch监听reactive定义的数据(一堆坑)
坑1:watch监听reactive定义的数据,oldValue居然跟newValue同步了
<template>
<div>
{{ person.name }}
{{ person.age }}
</div>
<button @click="handleChangeName">修改姓名</button>
<button @click="handleChangeAge">增加年龄</button>
</template>
<script>
import { ref, watch, reactive } from 'vue'
export default {
setup() {
let person = reactive({
name: '兰清',
age: 23
})
let handleChangeName = () => {
person.name += '1'
}
let handleChangeAge = () => {
person.age += 1
}
watch(person, (newValue, oldValue) => {
console.log('person变化了', newValue, oldValue); //oldValue居然跟newValue同步了
})
return {
person,
handleChangeName,
handleChangeAge
}
},
}
</script>
<style scoped></style>
坑2:要想监听ref定义的对象数据,watch第一个参数需要填xxx.value,否则无法监听变化
坑3:ref定义的数据xxx.value可以监听到变化,但是oldValue依然跟newValue同步了,原因在于ref处理对象数组类型的数据时内部依然借助reactive函数,所以现象跟监听reactive函数定义的数据是一样的
<template>
<div>
{{ person.name }}
{{ person.age }}
</div>
<button @click="handleChangeName">修改姓名</button>
<button @click="handleChangeAge">增加年龄</button>
</template>
<script>
import { ref, watch, reactive } from 'vue'
export default {
setup() {
let person = ref({
name: '兰清',
age: 23
})
let handleChangeName = () => {
person.value.name += '1'
}
let handleChangeAge = () => {
person.value.age += 1
}
// 1.无法监听变化
watch(person, (newValue, oldValue) => {
console.log('person变化了', newValue, oldValue);
})
// 2.可以监听变化,但是
watch(person, (newValue, oldValue) => {
//可以监听到变化,但是oldValue依然跟newValue同步了,原因在于ref处理对象数组类型的数据时内部依然借助reactive函数,所以现象跟监听reactive函数定义的数据是一样的
console.log('person变化了', newValue, oldValue);
})
return {
person,
handleChangeName,
handleChangeAge
}
},
}
</script>
<style scoped></style>
虽然oldValue不怎么需要,假如真想获得一个对象oldValue怎么办?:将想获得oldValue的属性单独用ref定义监听
坑3:watch监听reactive定义的数据深层次变化时默认deep:true,并且以前deep:false无效的问题修复了
<template>
<div>
姓名:{{ person.name }}
年龄:{{ person.age }}
薪水:{{ person.job.j1.salary }}
</div>
<button @click="handleChangeName">修改姓名</button>
<button @click="handleChangeAge">增加年龄</button>
<button @click="person.job.j1.salary++">加钱!</button>
</template>
<script>
import { ref, watch, reactive } from 'vue'
export default {
setup() {
let person = reactive({
name: '兰清',
age: 23,
job: {
j1: { salary: 13 }
}
})
let handleChangeName = () => {
person.name += '1'
}
let handleChangeAge = () => {
person.age += 1
}
// reactive定义的对象深层次变化时,可以直接监听到数据变化(可以理解为自动设置了deep:true)
watch(person, (newValue, oldValue) => {
console.log('newValue', newValue);
console.log('oldValue', oldValue);
}) //这个deep:false以前的版本设置了也没用依然会监听得到深层次的数据变化,但是现在vue3完善了,deep设置为false可以生效了
return {
person,
handleChangeName,
handleChangeAge
}
},
}
</script>
<style scoped></style>
坑4:监听reactive定义数据的某个属性
直接监听reactive数据的某一个属性会有警告且无效:
<template>
<div>
姓名:{{ person.name }}
年龄:{{ person.age }}
薪水:{{ person.job.j1.salary }}
</div>
<button @click="handleChangeName">修改姓名</button>
<button @click="handleChangeAge">增加年龄</button>
<button @click="person.job.j1.salary++">加钱!</button>
</template>
<script>
import { ref, watch, reactive } from 'vue'
export default {
setup() {
let person = reactive({
name: '兰清',
age: 23,
job: {
j1: { salary: 13 }
}
})
let handleChangeName = () => {
person.name += '1'
}
let handleChangeAge = () => {
person.age += 1
}
// 这样直接监听person.name会有警告
watch(person.name, (newValue) => {
console.log('newValue', newValue);
})
return {
person,
handleChangeName,
handleChangeAge
}
},
}
</script>
<style scoped></style>
警告内容:
因为watch监听的数据源只有:
- Getter/effect function:一个返回值用于观察的函数。
- Ref:通过
ref()创建的基本数据类型的响应式引用。 - Reactive object:通过
reactive()创建的对象类型的响应式代理。 - Array:包含上述任意类型的一个数组,用于同时监听多个源。
所以需要修改数据源:
// 监听数据源改为一个函数可以监听成功
watch(() => person.name, (newValue) => {
console.log('newValue', newValue);
})
注意监听这种普通类型的数据时可以拿到oldValue!!!!!
<template>
<div>
姓名:{{ person.name }}
年龄:{{ person.age }}
薪水:{{ person.job.j1.salary }}
</div>
<button @click="handleChangeName">修改姓名</button>
<button @click="handleChangeAge">增加年龄</button>
<button @click="person.job.j1.salary++">加钱!</button>
</template>
<script>
import { ref, watch, reactive } from 'vue'
export default {
setup() {
let person = reactive({
name: '兰清',
age: 23,
job: {
j1: { salary: 13 }
}
})
let handleChangeName = () => {
person.name += '1'
}
let handleChangeAge = () => {
person.age += 1
}
watch(() => person.name, (newValue, oldValue) => {
console.log(newValue, oldValue); //兰清1 兰清
}, { deep: true })
return {
person,
handleChangeName,
handleChangeAge
}
},
}
</script>
<style scoped></style>
坑5 监视reactive所定义的一个响应式数据中的某些属性
// 监听reactive定义数据里的多个属性
watch([() => person.name, () => person.age], (newValue) => {
console.log('newValue', newValue);
})
坑6 监听reactive定义数据的深层次结构属性
<template>
<div>
姓名:{{ person.name }}
年龄:{{ person.age }}
薪水:{{ person.job.j1.salary }}
</div>
<button @click="handleChangeName">修改姓名</button>
<button @click="handleChangeAge">增加年龄</button>
<button @click="person.job.j1.salary++">加钱!</button>
</template>
<script>
import { ref, watch, reactive } from 'vue'
export default {
setup() {
let person = reactive({
name: '兰清',
age: 23,
job: {
j1: { salary: 13 }
}
})
let handleChangeName = () => {
person.name += '1'
}
let handleChangeAge = () => {
person.age += 1
}
watch(() => person.job, (newValue) => {
console.log('newValue', newValue);
}, { deep: true }) //监听深层结构的属性需要加deep:true,跟直接监听整个reactive不一样了!!!
return {
person,
handleChangeName,
handleChangeAge
}
},
}
</script>
<style scoped></style>
总结
最后总结一下关于监听reactive定义的数据时oldValue的坑:只要监听是对象数组类型oldValue拿不到,普通类型可以拿到
监听ref所定义的对象数据的问题
直接监听失效
<template>
<div>
姓名:{{ person.name }}
年龄:{{ person.age }}
薪水:{{ person.job.j1.salary }}
爱好:{{ person.habit }}
</div>
<button @click="person.name += '1'">修改姓名</button>
<button @click="person.age++">增加年龄</button>
<button @click="person.job.j1.salary++">加钱!</button>
<button @click="person.habit.push(1)">改变爱好</button>
</template>
<script>
import { ref, watch, reactive } from 'vue'
export default {
setup() {
let person = ref({
name: '兰清',
age: 23,
job: {
j1: { salary: 13 }
},
habit: []
})
console.log('person', person);
// 无法触发监听,因为person本质上是一个RefImpl实例,我更改的数据实际上是更改这个RefImpl里面的.value
// 这个.value本质上是一个借助Reactive生成的Proxy对象,所以要想监听成功除非让这个代理对象整个内存地址发生变化
watch(person, (newValue, oldValue) => {
console.log('newValue', newValue);
console.log('oldValue', oldValue);
})
return {
person,
}
},
}
</script>
<style scoped></style>
解决办法1:监听.value 其本质是监听一个reactive定义的数据
watch(person.value, (newValue, oldValue) => {
console.log('newValue', newValue);
console.log('oldValue', oldValue);
})
解决办法2:设置deep:true
watch(person, (newValue, oldValue) => {
console.log('newValue', newValue);
console.log('oldValue', oldValue);
}, { deep: true })
watchEffect函数
1.watch的套路是:既要指明监视的属性,也要指明监视的回调
2.watchEffect的套路是:不用指明监视哪个属性,监视的回调中用到哪个属性,就监视哪个属性
3.watchEffect有点像computed(可以跟面试官吹牛)。但是computed注重的是计算出来的值(回调函数的返回值),所以必须要写返回值。watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值
4.watchEffect组件一加载就会触发一次回调
<template>
<div>
姓名:{{ person.name }}
年龄:{{ person.age }}
薪水:{{ person.job.j1.salary }}
爱好:{{ person.habit }}
数字:{{ num }}
</div>
<button @click="person.name += '1'">修改姓名</button>
<button @click="person.age++">增加年龄</button>
<button @click="person.job.j1.salary++">加钱!</button>
<button @click="person.habit.push(1)">改变爱好</button>
<button @click="num++">改变数字</button>
</template>
<script>
import { ref, watch, reactive, watchEffect } from 'vue'
export default {
setup() {
let num = ref(0)
let person = reactive({
name: '兰清',
age: 23,
job: {
j1: { salary: 13 }
},
habit: []
})
console.log('person', person);
// 1.一上来就执行一次回调
// 2.watchEffect函数体里用到了哪个响应式数据,就会监听此数据
watchEffect(() => {
let a = person.job.j1.salary //能够监听person.job.j1.salary
let b = num.value
console.log('watchEffect回调执行了');
})
return {
person,
num
}
},
}
</script>
<style scoped></style>
watch与watchEffect的区别(面试题,4点)
watch 和 watchEffect 都是监听器,watchEffect 是一个副作用函数。它们之间的区别有:
-
watch:既要指明监视的数据源,也要指明监视的回调。 -
而
watchEffect可以自动监听数据源作为依赖。不用指明监视哪个数据,监视的回调中用到哪个数据,那就监视哪个数据。 -
watch可以访问改变之前和之后的值,watchEffect只能获取改变后的值。 -
watch运行的时候不会立即执行,值改变后才会执行,而watchEffect运行后可立即执行。这一点可以通过watch的配置项immediate改变。 -
watchEffect有点像computed:- 但
computed注重的计算出来的值(回调函数的返回值), 所以必须要写返回值。 - 而
watcheffect注重的是过程(回调函数的函数体),所以不用写返回值
- 但
生命周期
Options API的生命周期:
beforeCreate: 在实例初始化之后、数据观测(initState)和 event/watcher 事件配置之前被调用。 对于此时做的事情,如注册组件使用到的store或者service等单例的全局物件。 相比Vue2没有变化。created: 一个新的 Vue 实例被创建后(包括组件实例),立即调用此函数。 在这里做一下初始的数据处理、异步请求等操作,当组件完成创建时就能展示这些数据。 相比Vue2没有变化。beforeMount: 在挂载之前调用,相关的render函数首次被调用,在这里可以访问根节点,在执行mounted钩子前,dom渲染成功,相对Vue2改动不明显。onMounted: 在挂载后调用,也就是所有相关的DOM都已入图,有了相关的DOM环境,可以在这里执行节点的DOM操作。在这之前执行beforeUpdate。beforeUpdate: 在数据更新时同时在虚拟DOM重新渲染和打补丁之前调用。我们可以在这里访问先前的状态和dom,如果我们想要在更新之前保存状态的快照,这个钩子非常有用。相比Vue2改动不明显。onUpdated:在数据更新完毕后,虚拟DOM重新渲染和打补丁也完成了,DOM已经更新完毕。这个钩子函数调用时,组件DOM已经被更新,可以执行操作,触发组件动画等操作beforeUnmount:在卸载组件之前调用。在这里执行清除操作,如清除定时器、解绑全局事件等。onUnmounted:在卸载组件之后调用,调用时,组件的DOM结构已经被拆卸,可以释放组件用过的资源等操作。
onActivated– 被keep-alive缓存的组件激活时调用。onDeactivated– 被keep-alive缓存的组件停用时调用。onErrorCaptured– 当捕获一个来自子孙组件的错误时被调用。此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回false以阻止该错误继续向上传播。
Composition API的生命周期:
除了beforecate和created(它们被setup方法本身所取代),我们可以在setup方法中访问的上面后面9个生命钩子选项:
自定义hook函数
- 什么是hook?--本质上是一个函数,把setup函数中使用的Composition API进行了封装。
- 类似于Vue2的mixin
- 自定义hook的优势:复用代码,让setup中的逻辑更加清楚易懂
案例:
usePoint.js
import { reactive, onMounted, onBeforeUnmount } from 'vue'
export function getMousePoint() {
let point = reactive({
x: 0,
y: 0
})
function savePoint(event) {
point.x = event.pageX
point.y = event.pageY
}
onMounted(() => {
window.addEventListener('click', savePoint)
})
onBeforeUnmount(() => {
window.removeEventListener('click', savePoint)
})
return point
}
Demo.vue
<template>
<div>鼠标点击的坐标:x:{{ point.x }} y:{{ point.y }}</div>
</template>
<script>
import { getMousePoint } from '../hooks/usePoint.js'
export default {
setup() {
let point = getMousePoint()
return {
point
}
},
}
</script>
<style scoped></style>
toRef和toRef(很有用)
案例:当我面对模板上需要显示很多对象的属性时,本来代码是这么写的
<template>
<div>
姓名:{{ person.name }}
年龄:{{ person.age }}
薪资:{{ person.job.j1.salary }}
<br />
<button @click="person.name += 1">改变姓名</button>
<button @click="person.age += 1">改变年龄</button>
<button @click="person.job.j1.salary += 1">加钱</button>
</div>
</template>
<script>
import { reactive } from 'vue'
export default {
setup() {
let person = reactive({
name: '兰清',
age: 23,
job: {
j1: {
salary: 13
}
}
})
return {
person
}
},
}
</script>
<style scoped></style>
但是我不想写那么一大串所以自己尝试进行了所谓的“优化”
<template>
<div>
姓名:{{ name }}
年龄:{{ age }}
薪资:{{ salary }}
<br />
<button @click="name += 1">改变姓名</button>
<button @click="age += 1">改变年龄</button>
<button @click="salary += 1">加钱</button>
</div>
</template>
<script>
import { reactive } from 'vue'
export default {
setup() {
let person = reactive({
name: '兰清',
age: 23,
job: {
j1: {
salary: 13
}
}
})
return {
name: person.name,
age: person.age,
salary: person.job.j1.salary
}
},
}
</script>
<style scoped></style>
这么写,我发现无法触发响应式了!
模拟一下响应式过程:
let name=p.name 只是单纯创建一个字符串变量无法触发响应式
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
let person = {
name: '兰清',
age: 23
}
let p = new Proxy(person, {
set(target, propName, value) {
console.log(`${propName}被修改了,我要去更新界面了`);
Reflect.set(target, propName, value)
}
})
let name = p.name //name是自己新定义的变量,单纯的一个字符串变量,已经失去了响应式效果
</script>
</body>
</html>
所以这种情况就得使用toRef了
语法:
const name = toRef(person, 'name') //name2是个ref对象
console.log('name', name); //name2是个ref对象
toRef创建的数据其内存指向概念图,其本质依然是指向person.name
用toRef改造代码:响应式实现,并且person对象里的数据会同步
<template>
<div>
{{ person }}
姓名:{{ name }}
年龄:{{ age }}
薪资:{{ salary }}
<br />
<button @click="name += 1">改变姓名</button>
<button @click="age += 1">改变年龄</button>
<button @click="salary += 1">加钱</button>
</div>
</template>
<script>
import { reactive, toRef } from 'vue'
export default {
setup() {
let person = reactive({
name: '兰清',
age: 23,
job: {
j1: {
salary: 13
}
}
})
return {
name: toRef(person, 'name'), //响应式实现,并且person对象里的数据会同步
age: toRef(person, 'age'),
salary: toRef(person.job.j1, 'salary'),
person
}
},
}
</script>
<style scoped></style>
注意:一眼看过去我觉得ref也能实现toRef的功能,但是用Ref创建的是一个独立的Ref对象,所以无法做到与person同步
<template>
<div>
{{ person }}
<br />
姓名:{{ name }}
年龄:{{ age }}
薪资:{{ salary }}
<br />
<button @click="name += 1">改变姓名</button>
<button @click="age += 1">改变年龄</button>
<button @click="salary += 1">加钱</button>
</div>
</template>
<script>
import { ref, reactive, toRef } from 'vue'
export default {
setup() {
let person = reactive({
name: '兰清',
age: 23,
job: {
j1: {
salary: 13
}
}
})
return {
name: ref(person.name), //响应式实现,并且person对象里的数据会同步
age: ref(person.age),
salary: ref(person.job.j1.salary),
person
}
},
}
</script>
<style scoped></style>
toRefs
const p = toRefs(person)
console.log('p', p);
输出结果
利用toRefs完善:
<template>
<div>
{{ person }}
<br />
姓名:{{ name }}
年龄:{{ age }}
薪资:{{ job.j1.salary }}
<br />
<button @click="name += 1">改变姓名</button>
<button @click="age += 1">改变年龄</button>
<button @click="job.j1.salary += 1">加钱</button>
</div>
</template>
<script>
import { ref, reactive, toRef, toRefs } from 'vue'
export default {
setup() {
let person = reactive({
- name: '兰清',
age: 23,
job: {
j1: {
salary: 13
}
}
})
return {
...toRefs(person), //toRefs返回一个对象,可以利用...将其展开方便在模板使用
person
}
},
}
</script>
<style scoped></style>
总结
toRef与toRefs
- 作用:创建一个ref对象,其value值指向另一个对象中的某个属性
- 应用:要将响应式对象中的某个属性单独提供给外部使用时
shallowReactive与shallowRef
- shallowReactive:只处理对象最外层属性的响应式(浅响应式)
- shallowRef:只处理基本数据类型的响应式,不进行对象的响应式处理
什么时候用?
- 如果有一个对象数据,结构比较深,但变化时只是外层属性变化===>shallowReactive
- 如果有一个对象数据,确定不会修改对象的属性,而是生成新的对象来替换===>shallowRef
readonly与shallowReadonly
- readonly:接收一个ref对象、reactive对象、普通对象,让一个响应式数据变为只读的(深只读)
- shallowReadonly:让一个响应式数据变为只读的(浅只读)
- 应用场景:不希望数据被修改时
toRaw与markRaw
toRaw:
- 作用:将一个由
reactive生成的响应式对象转为普通对象 - 使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新
markRaw:
- 作用:标记一个对象,使其永远不会再成为响应式对象
- 应用场景:1.有些值不应被设置为响应式的,例如复杂的第三方库等 2.当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能。
customRef
作用:创建一个自定义的ref,并对其依赖项跟踪和更新触发进行显式控制。
案例:
<template>
<div>
<input type="text" v-model="keyWord" />
<h3>{{ keyWord }}</h3>
</div>
</template>
<script>
import { ref, customRef } from 'vue'
export default {
setup() {
// 自定义一个ref--名为myRef
function myRef(value, delay) {
let timer
return customRef((track, trigger) => {
return {
get() {
console.log(`有人从myRef这个容器中读取数据了,我把${value}给他了`);
track()//通知vue追踪数据的变化(提前和get商量一下,让他认为这个value是有用的)
return value
},
set(newValue) {
console.log(`有人把myRef这个容器中数据改为了,${newValue}`);
clearTimeout(timer)
timer = setTimeout(() => {
value = newValue
trigger()//通知vue去重新解析模板
}, delay)
}
}
})
}
let keyWord = myRef('hello', 500)//实现myRef防抖维护数据
return {
keyWord
}
},
}
</script>
<style scoped></style>
provide与inject
作用:实现祖孙通信
套路:父组件有一个provide来提供数据,子组件有一个inject来使用这些数据
具体写法:
祖先组件
provide('car',car)
孙组件
const car=inject('car')
响应式数据的判断
- isRef:检查一个值是否是一个ref对象
- isReactive:检查一个对象是否是由reactive创建的响应式对象
- isReadonly:检查一个对象是否是由readonly创建的只读代理对象
- isProxy:检查一个对象是否是由reactive或者readonly方法创建的代理
新的组件
Fragment
- 在Vue2中:组件必须有一个根标签
- 在Vue3中:组件可以没有根标签,内部会将多个标签包含在一个
Fragment虚拟元素中 - 好处:减少标签层级,减少内存占用
Teleport
背景:比如深层次结构时,一个在底层组件的弹窗会受到其许多上级组件的影响,比如样式
使用teleport组件做完善
Dialog.vue
<template>
<div>
<button @click="handleShowDialog">点击弹窗</button>
<teleport to="body">
<div class="dialog" v-if="isShow">
<h3>我是一个弹窗</h3>
<h4>一些内容</h4>
<h4>一些内容</h4>
<h4>一些内容</h4>
<button @click="handleCloseDialog">关闭弹窗</button>
</div>
</teleport>
</div>
</template>
<script>
import { ref } from 'vue'
export default {
name: 'Dialog',
setup() {
let isShow = ref(false)
let handleShowDialog = () => {
isShow.value = true
}
let handleCloseDialog = () => {
isShow.value = false
}
return {
isShow,
handleShowDialog,
handleCloseDialog
}
}
}
</script>
<style scoped>
.dialog {
width: 300px;
height: 300px;
background-color: green;
}
</style>
to的值可以是一个 CSS 选择器字符串,也可以是一个 DOM 元素对象。这段代码的作用就是告诉 Vue“把以下模板片段传送到body标签下”。
会发现Dialog组件中被teleport组件包裹的部分被传送到body标签下了,这使得弹窗样式摆脱父级元素的影响,可以直接参考body标签样式。
Suspense
Suspense本质是用一个slot写的
背景:异步加载一个组件,会让App组件先显示,再显示Son组件,这会使页面比较抖动。
<template>
<div class="app">
<h3>我是APP组件</h3>
<Son />
</div>
</template>
<script>
// import Son from './components/Son.vue' 静态引入,等加载最慢的组件渲染成功,才显示整个页面
import { defineAsyncComponent } from 'vue';
const Son = defineAsyncComponent(() => import('./components/Son.vue')) //动态引入
export default {
components: { Son },
setup() {
},
}
</script>
<style scoped>
.app {
background-color: gray;
padding: 10px;
}
</style>
Suspense组件与动态引入可以让setup返回一个Promise
<template>
<div class="son">
<h4>我是Son组件</h4>
{{ num }}
</div>
</template>
<script>
import { ref } from 'vue'
export default {
async setup() {
let sum = ref(0)
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ sum })
}, 3000)
})
return await p
},
}
</script>
<style scoped>
.son {
background-color: skyblue;
padding: 10px;
}
</style>
总结
作用:等待异步组件时渲染一些额外内容,让应用有更好的用户体验
使用步骤:
- 异步引入组件
- 使用Suspense包裹组件,并配置好default与fallback