当前位置:网站首页 > Vue.js开发 > 正文

巧妙的响应式:深入理解Vue 3的响应式机制

注:本文是大圣老师课程的笔记,原课程地址:07 | 巧妙的响应式:深入理解Vue 3的响应式机制

一,什么是响应式

看下面的代码,double依赖于count,但是当我们修改了count,double却没有发生对应的变化。

let count = 1 let double = count * 2 console.log(double)//2 count = 2 console.log(double)//2 

想要实现count变化后,double自动更新。就需要一个东西去实时监听count,当发现count变化了,就再次调用double = count * 2更新下。如下:

创建一个监听的对象object let count = 1 //object开始监听count let double = count * 2 console.log(double)//2 count = 2 //object监听count到count发生变化了。就再次调用double = count * 2,更新double console.log(double)//2,实际上我们想要的是它变成4,这个操作应该是监听者object再次调用double = count * 2实现 

image-20211122233737247

二,响应式的原理

Vue 中用过三种响应式解决方案,分别是 definePropertyProxyvalue setter

我们首先来看 Vue 2 的 defineProperty API,这个函数详细的 API 介绍你可以直接访问MDN 介绍文档Object.defineProperty() - JavaScript | MDN (mozilla.org)来了解。

在下面的代码中,我们定义个一个对象 obj,使用 defineProperty 监听了 count 属性。这样我们就对 obj 对象的 value 属性实现了拦截,读取 count 属性的时候执行 get 函数,修改 count 属性的时候执行 set 函数,并在 set 函数内部重新计算了 double。

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 有种自动变化的感觉 

这样我们就实现了简易的响应式功能。

但 defineProperty API 作为 Vue 2 实现响应式的原理,它的语法中也有一些缺陷。比如在下面代码中,我们删除 obj.count 属性(删除的是堆空间中的obj.count),set 函数就不会执行,double 还是之前的数值(double还是全局变量)。这也是为什么在 Vue 2 中,我们需要 $delete 一个专门的函数去删除数据。

delete obj.count console.log(double) // doube还是4 

而Vue 3 的响应式机制是基于 Proxy 实现的,Proxy 的重要意义在于它解决了 Vue 2 响应式的缺陷。

Proxy和Math,Date一样,是js的内置对象。

我们看下面的代码,在其中我们通过 new Proxy 代理了 obj 这个对象,然后通过 get、set 和 deleteProperty 函数代理了对象的读取、修改和删除操作,从而实现了响应式的功能。

let obj = { 
   } let count = 1 let getDouble = n=>n*2 let double = getDouble(count) let proxy = new Proxy(obj,{ 
    get : function (target,prop) { 
    return target[prop] }, set : function (target,prop,value) { 
    target[prop] = value; if(prop==='count'){ 
    double = getDouble(value) } }, deleteProperty(target,prop){ 
    delete target[prop] if(prop==='count'){ 
    double = NaN } } }) proxy.count=count console.log(obj.count,double)//1 2 proxy.count = 2 console.log(obj.count,double) //2 4 delete proxy.count // 删除属性后,我们打印log时,输出的结果就会是 undefined NaN console.log(obj.count,double) //undefined NaN 

我们从这里可以看出 Proxy 实现的功能和 Vue 2 的 definePropery 类似,它们都能够在用户修改数据的时候触发 set 函数,从而实现自动更新 double 的功能。而且 Proxy 还完善了几个 definePropery 的缺陷,比如说可以监听到属性的删除。

Proxy 是针对对象来监听,而不是针对某个具体属性,所以不仅可以代理那些定义时不存在的属性,还可以代理更丰富的数据结构,比如 Map、Set 等,并且我们也能通过 deleteProperty 实现对删除操作的代理。

当然,为了帮助理解 Proxy,我们还可以把 double 相关的代码都写在 set 和 deleteProperty 函数里进行实现。比如下面代码中,Vue 3 的 reactive 函数可以把一个对象变成响应式数据,而 reactive 就是基于 Proxy 实现的。我们还可以通过 watchEffect,在 obj.count 修改之后,执行数据的打印。

import { 
    reactive, watchEffect, computed } from "vue"; let obj = reactive({ 
    count: 1 }); let double = computed(() => obj.count * 2); setTimeout(() => { 
    obj.count = 2; }, 3000); watchEffect(() => { 
    console.log("数据被修改了", obj.count, double.value); }); 

有了 Proxy 后,响应式机制就比较完备了。但是在 Vue 3 中还有另一个响应式实现的逻辑,就是利用对象的 get 和 set 函数来进行监听,这种响应式的实现方式,只能拦截某一个属性的修改,这也是 Vue 3 中 ref 这个 API 的实现。在下面的代码中,我们拦截了 count 的 value 属性,并且拦截了 set 操作,也能实现类似的功能。

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) 

三种实现原理的对比表格如下:

image-20211123003224387

三,定制响应式数据

setup重构之后的 todolist 的代码。这段代码使用 watchEffect,数据变化之后会把数据同步到 localStorage 之上,这样我们就实现了 todolist 和本地存储的同步。

function useStorage(name, value=[]){ 
    let data = ref(JSON.parse(localStorage.getItem(name)|| value)) watchEffect(()=>{ 
    localStorage.setItem(name,JSON.stringify(data.value)) }) return data } 

更进一步,我们可以直接抽离一个 useStorage 函数,在响应式的基础之上,把任意数据响应式的变化同步到本地存储。我们先看下面的这段代码,ref 从本地存储中获取数据,封装成响应式并且返回,watchEffect 中做本地存储的同步,useStorage 这个函数可以抽离成一个文件,放在工具函数文件夹中。

function useStorage(name, value=[]){ 
    let data = ref(JSON.parse(localStorage.getItem(name)|| value)) watchEffect(()=>{ 
    localStorage.setItem(name,JSON.stringify(data.value)) }) return data } 

在项目中我们使用下面代码的写法,把 ref 变成 useStorage,这也是 Composition API 最大的优点,也就是可以任意拆分出独立的功能。

let todos = useStorage('todos',[]) function addTodo() { 
    ...code } 

我们可以把日常开发中用到的数据,无论是浏览器的本地存储,还是网络数据,都封装成响应式数据,统一使用响应式数据开发的模式。这样,我们开发项目的时候,只需要修改对应的数据就可以了。

四,reactive 正确使用姿势

reactive函数传递的参数必须是对象(json/arr) 千万不要这样写 let objData=reactive({ 
    name:'林漾', age:31, sex:'女' }) 这样写是非常的不好的。 有的小伙伴可能会说: 上面reactive函数传递的参数是对象呀 有什么问题了??? 问题是在我们跟新数据的时候一点都不友好 还有就是我们在实际开发过程中可能有好几处都是响应式的数据 这个时候我们只需要创建一个reactive 就说按照下面这样来处理 let objData=reactive({ 
    // 某一个区域使用的数据 oneObj:[ { 
    name:'林漾', age:31, sex:'女' } ], // 另一个区域使用的数据 two:{ 
    name:'余声声', age:123 } }); 不推荐这样使用 let oneObj=reactive({ 
    name:'林漾', age:31, sex:'女' }) let two=reactive({ 
    name:'余声声', age:123 }) 

五,Vueuse 工具包

我们自己封装的 useStorage,算是把 localStorage 简单地变成了响应式对象,实现数据的更新和 localStorage 的同步。同理,我们还可以封装更多的类似 useStorage 函数的其他 use 类型的函数,把实际开发中你用到的任何数据或者浏览器属性,都封装成响应式数据,这样就可以极大地提高我们的开发效率。

Vue 社区中其实已经有一个类似的工具集合,也就是 VueUse,它把开发中常见的属性都封装成为响应式函数。

vueuse的安装:

npm install @vueuse/core 

简单使用:

import { 
    useMouse } from "@vueuse/core"; // "x" and "y" are refs const { 
    x, y } = useMouse(); console.log( x.value, y.value); 
到此这篇巧妙的响应式:深入理解Vue 3的响应式机制的文章就介绍到这了,更多相关内容请继续浏览下面的相关推荐文章,希望大家都能在编程的领域有一番成就!

版权声明


相关文章:

  • 手把手写一个vue3的组件2024-11-27 23:45:10
  • vue+webpack5项目中全局引入scss2024-11-27 23:45:10
  • 安装vue-devtool2024-11-27 23:45:10
  • copyWebpackPlugin的使用及常见问题(glob及Path ............... is not in cwd)2024-11-27 23:45:10
  • vue项目history路由的配置2024-11-27 23:45:10
  • js基础-(二)-类和面向对象2024-11-27 23:45:10
  • vue3代码的组织形式2024-11-27 23:45:10
  • vue3初探-工程化项目架构-笔记2024-11-27 23:45:10
  • vue-cli2.0webpack的理解2024-11-27 23:45:10
  • 对修饰器的实验支持功能在将来的版本中可能更改。在 “tsconfig“ 或 “jsconfig“ 中设置 “experimentalDecorators“ 选项以删除此警告。ts(1219)2024-11-27 23:45:10
  • 全屏图片