Vue3 学习笔记
1、Vue3 响应式原理
浅浅的先复习一下Vue2的响应式原理
1.1—— 回忆Vue2的响应式原理是:
1、对象类型使用Object.defineProperty()
对属性的读取、修改进行拦截操作,即数据劫持。
2、数组类型是通过重写更新数组的一系列方法来实现拦截操作。(对数组的更新方法进行了包裹)。
缺点
- 新增属性、删除属性,视图是不会更新的。
- 直接通过下标修改数组,界面不会更新。
<script>
// Vue2响应式原理 通过Object.defineProperty() 对属性的读取,修改的操作进行拦截,即数据劫持
// 1、VM.$data就是原始数据 , Vue的实例实际上也代理了data对象上的property, 因此访问VM.a === VM.$data.a
// 2、当原始数据的属性以 ‘_’ 和 ‘$’ 开头,则不会被Vue实例代理, 因为这些属性可能会与Vue内置的property、API发生冲突
// 原始数据对象 相当于 VM.$data
let person = {
name:"张三",
age: 18
}
// 模拟Vue2响应式原理
// Vue实例代理的data对象上的所有property
let p = {}
Object.defineProperty(p, "name", {
enumerable: true, // 可枚举的
configurable: true, // 属性是否可删除
get(){
console.log("有人读取了obj的name属性")
return person.name
},
set(value){
person.name = value
// 在这里进行更新视图的操作
console.log("name变化了,Vue就是在这实现数据变化后,视图也更新的操作");
}
})
Object.defineProperty(p, "age", {
get(){
console.log("有人读取了obj的age属性")
return person.age
},
set(value){
person.age = value
console.log("age变化了,Vue就是在这实现数据变化后,视图也更新的操作");
}
})
</script>
1.2——Vue3响应式原理
ref
是通过Object.defineProperty() 实现的响应式
reactive
是通过Proxy()
实例进行数据拦截,由Reflect
实现数据的获取、修改、新增、删除操作的。
实现原理:
- 通过
Proxy
代理:拦截对象中任意属性的变化,包括属性值的读取、修改、添加、删除、等 - 通过
Reflect
反射:对被代理对象的属性进行操作
<script>
// 原始数据
let person = {
name: "张三",
age: 18
}
// 模拟Vue3响应式原理
let p = new Proxy(person, {
get(target, propName) {
console.log(`有人读取了${propName}属性`);
// return target[propName]
return Reflect.get(target, propName)
},
set(target, propName, value) {
// target[propName] = value
Reflect.set(target, propName, value)
console.log(`有人修改${propName}属性, 界面开始更新`);
},
deleteProperty(target, propName) {
console.log(`有人删除了${propName}属性`);
return Reflect.deleteProperty(target, propName)
// return delete target[propName] // 返回删除的结果
}
})
</script>
1.3——什么是Object.defineProperty
1、Object.defineProperty(obj, prop, descriptor)
是一个声名对象属性的方法
参数:
obj
:要定义属性的对象。
prop
:要定义或修改的属性的名称或 Symbol
。
descriptor
:要定义或修改的属性描述符。
descriptor
的配置项
属性 | 描述 | 默认值 |
---|---|---|
configurable | 当该属性的值为true时,该属性才可以被delete删除 | false |
enumerable | true 只有在枚举相应对象上的属性时该属性显现。 | false |
value | 该属性的值 | undefined |
writable | true 只有与该属性相关联的值被assignment operator (en-US)改变时。 | false |
get | 作为属性的getter函数,读取改属性的时候调用 | undefined |
set | 作为属性的setter函数,修改改属性的时候调用 | undefined |
<script>
let p = {}
Object.defineProperty(obj, prop, {
configurable: false,
enumerable: false,
})
</script>
2、descriptor又是什么?
这就相当于一个配置项,具体配置如上表所示。
也就是控制一个对象属性的获取、修改、新增、删除的配置。
1.4、 ref与reactive的对比
1.5、 setup的两个注意点
在Vue2中,再给组件传入props的时候,组件内部都需要声名props,但是在组件内部未声名props也可以获取从VueComponents实例的$attrs属性上获取到传给组件的props,只不过在调用的时候可能会麻烦写。使用插槽往组件内部插入动态插入DOM结构,这些Dom结构也会出现在VueComponents实例身上。
setup的执行时机:在beforeCreate之前会执行一次,且 this 是 undefined
2、computed函数
Vue3的computed不再和Vue2一样为配置项,但是在Vue3中一样可以使用computed配置项。
Vue3中的computed需要从vue模块中引入
import {computed} from 'vue'
Vue3计算属性的是一个函数
1.1 计算属性的简写形式
computed简写:
参数:一个arrow fucntion(箭头函数)computed( ( )=>{ } )
person.fullName = computed(()=>{
return person.firstName + '-' + person.lastName
})
2.2 计算属性的完整写法
computed完整写法:
参数: { get(){}, set(){} }
person.fullName = computed({
get(){
return person.firstName + '-' + person.lastName
},
set(newVal){ // newVal是修改后的值
const arr = newVal.split('-')
person.firstName = arr[0]
person.lastName = arr[1]
}
})
3、watch 函数
监视,监视的是一个地址、结构,无法直接监视某一个具体的值
就像ref所定义的响应式数据,let sum = ref(0)
可以监视sum, 但是不能直接监视sum.value
let sum = ref(0)
let message = ref('你好啊')
let person = reactive({
name:"张三",
age: 18,
job: {
j1:{
salary: 20
}
}
})
3.1 监视一个ref
所定义的响应式数据
/*
参数:
1、监视的对象,(监视多个属性时,可以使用数组)
2、回调函数 (newVal, oldVal) 回调函数接收两个参数:1、新值 2、旧值
3、监视属性的配置 immediate、deep
*/
// 情况一 监视一个ref所定义的一个响应式数据
watch(sum, (newVal, oldVal)=>{
console.log(`sum变了。新值: ${newVal} 旧值${oldVal}`);
}, {immediate:true, deep:true}) // 监视基本数据类型的时候deep是无意义的
3.2 监视多个ref
所定义的响应式数据
// 情况二 监视多个ref所定义响应式数据
watch([sum, message], (newVal, oldVal)=>{
console.log('sum or message changed', newVal, oldVal) // newVal, oldVal,也是以数组的形式来区分的
}, {immediate:true})
3.3 监视reactive所定义的一个响应式数据的所有属性
注意
1、此处无法正确的获取到
oldValue
,接收到的两个参数都是newValue
2、Vue3中的监视属性默认开启深度监视
deep:true;
注意:无法被关闭 。
// 情况三 监视reactive所定义的响应式数据的所有属性
/*
注意:
1、无法获取到oldValue
2、默认开启深度监视, 但是无法关闭{deep:false} 是无效的
*/
watch(person, (newVal, oldVal)=>{
console.log('person有变化了', newVal, oldVal);
}, {deep:false})
如何解决该问题?
切记,不可将reactive换成ref,因为ref在将对象做成响应式的时候,内部还是去使用了reactive的。
可以将需要监视对象的属性单独拿出来,使用ref所定义的响应式数据
3.4 监视reactive所定义的一个响应式数据的某个属性
注意:监视reactive所定义的响应式数据的某个属性时,watch函数的第一个参数不可以直接是对象的属性的形式, 应该写成函数的返回值形式
watch(()=>{}, ()=>{}, {}) // 监视的属性的回调函数,回调函数, 监视函数的配置
1、无法监视对象的属性值为基本数据类型,可以监视对象的属性为引用数据类型 2、如果需要监视的对象的属性为基本数据类型,那么watch的第一个参数应该写成函数的形式
// 监视对象的属性的为基本数据类型的数据
// 将watch的第一个参数应该写成函数的形式,可以解决无法获取到oldVal的情况
watch(()=>person.name, (newVal, oldVal)=>{
console.log('person.name变化了', newVal, oldVal);
})
3.5 特殊情况
这样子监视对象的时候依旧是无法获取到oldVal, 但是deep配置项是有效的,需要开启后才可以监视对象
/*
注意:
1、深度监视的配置项是有效的
2、仍然无法获取到oldVal
*/
watch(()=>person.job, (newVal,oldVal)=>{
console.log("person.job变化了", newVal, oldVal)
}, {deep: true})
4、watchEffect函数
4.1 watchEffect与watch的区别?
1、watch需要指明所监视的属性,也要指明监视的回调函数;而watchEffect不需要指明监视的属性,只是在监视回调函数中用到哪个属性,就监视哪个属性。
4.2 watchEffect有点和computed相似。
computed: 只有在computed依赖的属性发生变化的时候会重新计算,且多次读取仅计算一次,因为computed更注重,所以一定需要返回值
watchEffect:监视回调函数,用到哪个属性就监视哪个属性。其更注重过程(回调函数)所以,不用写返回值
let sum = ref(0)
let message = ref('你好啊')
let person = reactive({
name:"张三",
age: 18,
job: {
j1:{
salary: 20
}
}
})
// 所以这个watchEffect 会监视sum、person.job.j1.salary属性
watchEffect(()=>{
let x1 = sum.value
let salary = person.job.j1.salary
console.log("watchEffect函数被调用了", x1, salary)
})
5、生命周期
Vue3的生命周期与Vue2的生命周期的区别?
1、Vue3的生命周期在组合式api中没有了beforeCreate、和created这两个钩子相当于setup()
2、Vue3中的摧毁的生命周期钩子名称被替换了,具体见3 。
3、其他对应的生命周期为
Vue2 Vue3
beforeCreate
====>setup()
created
====>setup()
beforeMount
====>onBeforeMount
mounted
====>onMounted
beforeUpdate
====>onBeforeUpdate
update
====>onUpdate
beforeDestory
====>onBeforeUnmount
destory
====>onUnmounted
6、自定义hooks
hooks就是将多个组件中公用的的地方提取出来,在vue2中也有一个mixin混入,和这个差不多。、
hooks规范化:
1、需要在src目录下创建hooks文件夹,然后里面都是一些js文件,这些文件的命名规则 useXxxx
需要在前面加上use
2、组件需要用的话,可以直接引入
// usePoint.js
import {reactive, onMounted, onBeforeUnmount} from 'vue'
export default function(){
let point = reactive({
x: 0,
y: 0
})
function getPoint(event){
console.log("event", event);
point.x = event.clientX
point.y = event.clientY
}
onMounted(()=>{
console.log('移除getPoint监听事件');
window.addEventListener('click', getPoint)
})
onBeforeUnmount(()=>{
console.log('移除getPoint监听事件');
window.removeEventListener('click', getPoint)
})
return point
}
7、toRef
- 作用:创建一个ref对象,将其的value指向另外一个对象中的某个属性
- 语法:
const name = toRef(person, 'name')
- 应用:要将响应式对象中的某个属性单独提供给外部(模板…)使用时
- 扩展:toRef 与 toRefs 功能一致,但是可以批量创建多个ref对象 语法:
toRefs(person)
为何会出现这个API?
因为使用reactive所定义的响应式数据,在模板中使用的时候都需要使用该对象然后访问其需要的属性。为了去除每次访问其都需要通过reactive所定义的对象,为使其简单化,可以使用…toRefs
,也可以使用toRef
将响应式对象中的某个属性提取出来,并且是响应式的数据。
8、其他 composition API
8.1 shallowReactive与shallowRef
shallow: 浅层;浅层次的
- shallowReactive:只处理对象最外层的响应式(浅响应式)
- shallowRef:只处理基本数据类型的响应式,不对对象进行响应式处理。
- 什么时候用?
- 如果有一个对象数据,结构比较深,但是只需要监测外层的响应式变化时
- 如果有一个对象数据,后续功能不修改该对象中的属性,而是使用新的对象时
8.2 readOnly 与 shallowReadOnly
- readOnly:让一个响应式的数据变为只可读 (深只可读)
- shallowReadOnly:让一个响应式的数据变为只可读 (浅可读)
- 应用场景: 不希望数据被修改
import {reactive, shallowReadonly, readonly} from 'vue'
export default {
name: 'App',
setup() {
let person = reactive({
name:"张三",
age: 18,
job:{
j1:{
salary: 8000
}
}
})
// 浅可读:对象属性外面一层是只可读的,内层的属性是响应式的
let shallowPerson = shallowReadonly(person)
// 深可读
let deepReadOnlyPerson = readonly(person)
return {
person: deepReadOnlyPerson
}
}
}
8.2 toRaw 与 markRaw
- toRaw
- 作用:将一个reactive生成的响应式对象转为普通对象(非响应式的)
- 使用场景:用于将一个响应式对象转化成普通对象,对该对象的所有操作,都不会引发页面更新
- markRaw
- 作用:标记一个对象,使其永远不再变为响应式对象
- 应用场景:
- 有些响应式对象的属性的值不需要是响应式的,例如复杂的第三方类库…
- 当渲染具有不打可变的数据源列表时,跳过响应式转化的渲染消耗
9、 响应式数据的判断
Vue3官方提供了一些判断变量是否为响应式的方法
isRef
: 检查是否为一个ref
对象isReactive
:检查是否为一个reactive
所创建的响应式对象isReadOnly
:检查一个对象是否由readOnly
创建的只读对象isProxy
:检查一个对象是否是由reactive
或者readOnly
方法所创建的
10、Fragment组件
在Vue2中:组件必须有一个跟标签
但是在Vue3中:组件可以没有跟标签,内部的多个标签都会被包含在一个fragment虚拟元素中
好处:减少标签的层级嵌套, 减少内存占用
11、Teleport组件
什么是Teleport组件?
Teleport是一个能够将我们的HTML结构移动到我们指定位置的技术
<template>
<div class="container">
<button @click="showDialog = true">打开弹窗</button>
<!-- 这里将Dialog组件直接在body的子层渲染 -->
<teleport to="body">
<div class="model-container" v-if="showDialog"></div>
<div class="dialog-container" v-show="showDialog">
<h1>我是弹窗组件</h1>
<h2>Teleport组件</h2>
<p>
Teleport是一种能够将指定Html结构移动到指定位置上的技术
</p>
<button @click="showDialog = false">关闭弹窗</button>
</div>
</teleport>
</div>
</template>
12、Suspence组件
-
等待组件的异步引入,让用户有更好的体验
-
可以让主要的组件先渲染出来,然后其他的组件比较耗时的组件就异步引入,这样子可以防止一个组件出错,整个页面渲染失败
-
如何使用?使用步骤
- 首先异步引入(动态引入)组件
import {defineAsyncComponent} from "vue" const Demo = defineAsyncComponent(()=>import('./components/Demo.vue')
-
使用Suspense包裹组件,并配置好插槽default和fallback
-
default: 就是渲染成功要显示的dom结构
<template v-slot:default> <Demo/> </template>
-
fallback 就是未渲染或渲染失败显示的dom结构
<template v-slot:fallback> <h1>加载中... ...</h1> </template>
-
-
完整的DOM结构
<suspense>
<template v-slot:default>
<Demo/>
</template>
<template v-slot:fallback>
<h1>加载中... ...</h1>
</template>
</suspense>
13、Vue3的其他调整
13.1 API 调整![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/560b6751c3728e2c3578a0b175d8362b.png#pic_center)
13.2 Vue3动画样式调整
13.3 移除keyCode作为v-on的修饰符,同时也不在支持config.keyCodes
原因:浏览器对keyCode的兼容性问题,所以取消了这样的写法
13.4 移除v-on.native
修饰符
-
父组件中绑定的事件
<demo v-on:click="handleClick" v-on:close="handleClose" />
-
子组件声名自定义事件
<script> export default { emits:['close'] } </script>
13.5 移除过滤器
因为过滤器虽然看起来很方便,但是需要自定义语法,学习成本高,还有实现成本, 建议使用计算属性替换过滤器