vue共使用了三种响应式机制:defineProperty、Proxy、value setter
1.defineProperty【Vue2】
对象类型:通过object.defineProperty()对属性的读取、修改进行拦截(数据劫持)
数组类型:通过重写更新数组的一系列方法来实现拦截(对数组的变更方法进行了包裹)
let getDouble = n=>n*2
let obj = {}
let count = 1
let double = getDouble(count)
Object.defineProperty(obj,'count',{
get(){
return count
},
set(val){
count = val
double = getDouble(val)
}
})
console.log(double) // 打印2
obj.count = 2
console.log(double) // 打印4 有种自动变化的感觉
缺陷:对象新增属性、删除属性&&通过下标修改数组,界面不会自动更新
但也提供了以下属性来新增属性等......
// 对象新增属性
this.$set(this.person, sex, '女')
Vue.set(this.person, sex, '女') //使用Vue时需要导入一下import Vue from 'vue'
// 对象删除属性
this.$delete(this.person, sex)
Vue.delete(this.person, sex)
// 通过下标更新数组
this.$set(this.person.hobby, 0, '逛街')
this.person.hobby.splice(0, 1, '逛街')
优势:兼容性
2.Proxy【Vue3 reactive】
模拟vue3中实现响应式:
Reflect反射对象是es6的,object的一些属性基本它都有,这里是使用Reflect对源对象属性进行操作 Reflect介绍
let proxy = new Proxy(obj,{
// 读取
get : function (target,prop) {
// return target[prop]
return Reflect.get(target, prop)
},
// 修改和新增
set : function (target,prop,value) {
// target[prop] = value;
Reflect.set(target, prop, value)
},
// 删除
deleteProperty(target,prop){
// return delete target[prop]
return Reflect.deleteProperty(target, prop)
}
})
Proxy 的重要意义在于它解决了 Vue 2 响应式的缺陷。我们看代码,在其中我们通过 new Proxy 代理了 obj 这个对象,然后通过 get、set 和 deleteProperty 函数代理了对象的读取、修改&&新增 和删除操作,从而实现了响应式的功能。
Proxy 是针对对象来监听,而不是针对某个具体属性,所以不仅可以代理那些定义时不存在的属性,还可以代理更丰富的数据结构,比如 Map、Set 等,并且我们也能通过 deleteProperty 实现对删除操作的代理。
优势:基于Proxy实现真正的拦截
缺陷:兼容不了IE11,所以项目有对兼容性有要求的就用不了Vue3
应用:Vue3复杂数据结构
Vue 3 的 reactive 函数可以把一个对象变成响应式数据,而 reactive 就是基于 Proxy 实现的。我们还可以通过 watchEffect,在 obj.count 修改之后,执行数据的打印。
import {reactive,computed,watchEffect} from 'vue'
let obj = reactive({
count:1
})
let double = computed(()=>obj.count*2)
obj.count = 2
watchEffect(()=>{
console.log('数据被修改了',obj.count,double.value)
})
3.value setter(Vue3 ref)
let getDouble = n => n * 2
let _value = 1
double = getDouble(_value)
let count = {
get value() {
return _value
},
set value(val) {
_value = val
double = getDouble(_value)
}
}
console.log(count.value,double)
count.value = 2
console.log(count.value,double)
利用对象的 get 和 set 函数来进行监听,这种响应式的实现方式,只能拦截某一个属性的修改,这也是 Vue 3 中 ref 这个 API 的实现。在代码中,我们拦截了 count 的 value 属性,并且拦截了 set 操作,也能实现类似的功能。
优势:实现简单
缺陷:只拦截了value属性
应用:Vue3简单数据结构
举个例子:
在下面的代码中,我们把对图标的对应修改的操作封装成了 useFavicon 函数,并且通过 ref 和 watch 的包裹,我们还把小图标变成了响应式数据。
import {ref,watch} from 'vue'
export default function useFavicon( newIcon ) {
const favicon = ref(newIcon)
const updateIcon = (icon) => {
document.head
.querySelectorAll(`link[rel*="icon"]`)
.forEach(el => el.href = `${icon}`)
}
const reset = ()=>favicon.value = '/favicon.ico'
watch( favicon,
(i) => {
updateIcon(i)
}
)
return {favicon,reset}
}
这样一来,在组件中,我们就可以通过响应式的方式去修改和使用小图标,通过对 faivcon.value 的修改就可以随时更换网站小图标。
实现:在点击按钮之后,修改了网页的图标为 geek.png 的操作。
<script setup>
import useFavicon from './utils/favicon'
let {favicon} = useFavicon()
function loading(){
favicon.value = '/geek.png'
}
</script>
<template>
<button @click="loading">123</button>
</template>
VueUse工具包:将开发常见的属性封装为了响应式函数
安装、
npm install @vueuse/core
在下面这段代码中,我们使用 useFullscreen 来返回全屏的状态和切换全屏的函数。这样,我们就不需要考虑浏览器全屏的 API,而是直接使用 VueUse 响应式数据和函数就可以很轻松地在项目中实现全屏功能。
<template>
<h1 @click="toggle">click</h1>
</template>
<script setup>
import { useFullscreen } from '@vueuse/core'
const { isFullscreen, enter, exit, toggle } = useFullscreen()
</script>
useFullscreen 的封装逻辑和 useStorage 类似,都是屏蔽了浏览器的操作,把所有我们需要用到的状态和数据都用响应式的方式统一管理,VueUse 中包含了很多我们常用的工具函数,我们可以把网络状态、异步请求的数据、动画和事件等功能,都看成是响应式的数据去管理。