首页 > 其他分享 >vue的响应式原理:依赖追踪

vue的响应式原理:依赖追踪

时间:2023-12-05 16:36:27浏览次数:24  
标签:vue 渲染 watcher 响应 计算 追踪 data 模板 属性

在明白原理之前,我们有很多表面现象、使用场景需要记忆。明白了原理后,你会发现它们已经不需要记了,因为从原理出发,你自己都能把它们推导出来,一切是那么的自然而然。感觉就是:这还用记吗?很明显嘛!

之前我对vue的响应式原理,只是一知半解,导致开发中经常会出现疑问,比如:为什么有的数据它不响应?模板中用到的methods方法什么时候会执行?什么时候模板会重新渲染?渲染的过程是什么等等。所有的这些开发过程中的疑惑,都是因为不了解底层原理造成的。

今天我们就来一起捋一下,vue的响应式原理。当然,只是入门级的,可以帮助和我一样不了解原理的同学,大佬勿喷。

 

一、数据劫持:getter和setter

在vue的data初始化阶段,vue会递归地遍历data的每一个属性,把它们处理成响应式数据。这是一个深层次的遍历,也就是说data的属性如果是一个对象,这个对象的属性也是响应式的,不管嵌套几层。

具体来说,vue对每一个属性执行Object.defineProperty(),把每一个属性转换为getter和setter,以此实现对属性取值、赋值的劫持,称为数据劫持。

 

二、watcher和dep

我们知道,模板中会用到data的数据,计算属性也是如此,它们会各用一个列表来保存自己用到了哪些data数据,称为依赖列表。

而data的属性,则可能会被模板以及多个计算属性用到,它也会用一个列表来保存哪些模板或计算属性用到了自己,也叫依赖列表。

模板和计算属性,通过watcher对象来做这件事,依赖列表存放在watcher的一个数组里。每一个vue实例,有一个watcher,称为渲染watcher。每一个计算属性,各自有一个wathcer,称为计算属性watcher。

data的属性,通过dep对象来做这件事,依赖列表存放在dep的一个数组里。data的每一个属性,都有一个dep对象。

watcher的这个数组,成员是dep对象。dep的这个数组,成员是watcher对象。

也就是说,通过维护对方的列表,模板和计算属性,知道我用到了哪些属性。data的属性,也知道哪些模板和计算属性用到了我。

 

三、依赖收集

在模板第一次渲染、计算属性第一次被使用时,它们所依赖属性的getter会触发,然后就把这个模板或计算属性的watcher添加到该属性的依赖列表里(dep对象的数组)。

同时,这些属性的dep对象,也会被添加到模板或计算属性的依赖列表里(watcher对象的数组)。

这个过程是双向的。我曾经疑问为什么需要在watcher里维护依赖列表?因为看上去,属性更新时,通知它的依赖列表里的每一个watcher,让它们去更新,这个模型似乎就可以了。

原来,有时我们是需要模板主动更新的,比如$forceUpdate函数,这时通过watcher的依赖列表,就可以查看这些依赖有没有更新,如果都没有更新,就无需重新渲染,提高了性能。

 

四、依赖更新

在一个属性发生变化时,这个属性的setter被触发,它会通知依赖列表里的每一个watcher,让它们去更新。

渲染watcher接到通知,会重新渲染页面。计算属性watcher接到通知,会进行重新计算。

实际的模型比这要复杂。组件的更新过程是异步的,当被通知重新渲染时,不会立即触发,而是将组件标记为“待更新”。Vue 使用一个异步队列来批量处理这些更新,以提高性能。这意味着在同一事件循环中,多次改变数据只会导致一次组件更新和重新渲染。

同样,通知计算属性重新计算,也不会立即触发,而是把计算属性标记为“待更新”,直到该计算属性下一次被使用时(比如重新渲染),才会重新计算。

 

五、原理之上的应用

明白了原理,我们可以弄清楚很多问题,比如:

(1)vue中的哪些数据是响应式的?

props、data、computed:前两个我们好理解,这里需要注意的是计算属性。思考下面一个问题:

模板中用到一个计算属性,那么它的渲染watcher的依赖列表里,是这个计算属性,还是这个计算属性所依赖的data属性?

答案是:这个计算属性。这是因为,计算属性本身也是响应式的,同样会被Object.defineProperty处理。计算属性的效果就是一层缓存,它不仅会被模板用到,还可能被其他计算属性用到。在这个案例中,当计算属性依赖的data改变时,会先触发计算属性的重新计算,只有计算后的值和原来不同,模板才会重新渲染,反之,就无需重新渲染。

另外,$route和$store.state也是响应式的,原理和其他的一样。意味着,如果模板中用到了它俩,它俩改变时模板是会重新渲染的(计算属性也一样,会重新计算)。

(2)我们知道,一个模板中会用到各种数据:data属性、计算属性、表达式、methods中的方法、全局的自定义函数。那么当模板重新渲染时,它们各自会怎么样呢?

计算属性:只有计算属性的依赖发生变化时,它才会在重新渲染时重新计算。前者会把计算属性标记为“待更新”,重新计算则会等到下一次被使用(比如重新渲染)时才会进行。

表达式、methods中的方法、全局的自定义函数:每次重新渲染都会重新计算,因为它们的值不会被缓存,所以要尽可能多的使用计算属性。

(3)什么会触发组件的重新渲染?

组件只有在模板依赖的数据发生变化时,才会重新渲染。那些模板中没用到的数据,改变并不会让模板重新渲染。并且,这种依赖是属性级别的,也就是说,模板中用到了data中的一个对象,但这个对象的改变不一定导致重新渲染,因为改变的属性不一定是模板用到的那个。

父组件和子组件,它们的渲染也没有必然的联系。子组件的data发生变化,不会导致父组件重新渲染,因为父组件不会用到子组件的数据。父组件的data发生变化,也只有它自己,和用到该数据的子组件会重新渲染。不过要注意,如果父组件是销毁了重新创建,那么子组件也只能跟着销毁重新创建。另外,如果父组件对子组件使用了v-if、v-for(搭配key使用)、key,那么子组件很可能会随着它们的变化而销毁重建。

(4)为什么vue无法监听对象和数组的某些操作?

明白了vue2的响应式原理,也就理解了为什么,vue无法监听到对象属性的添加和删除,因为vue2只能劫持对象属性的取值和赋值。想给响应对象添加属性,应该使用Vue.set()或者this.$set()。

数组的限制是,无法监听到通过索引直接赋值和修改数组的长度。我暂时无法解释,不过我的方法时统一用splice方法来替代。

 

本人水平非常有限,写作主要是为了把自己学过的东西捋清楚。如有错误,还请指正,感激不尽。

标签:vue,渲染,watcher,响应,计算,追踪,data,模板,属性
From: https://www.cnblogs.com/luzeyu/p/17877545.html

相关文章

  • [转]vue3+tsx开发语法详解
    原文地址:vue3+tsx开发语法详解-知乎很多组件库都使用了TSX的方式开发,主要因为其灵活性比较高,TSX和SFC开发的优缺点就不介绍了,这里主要说一下将SFC项目改造为TSX的过程。安装JSX库pnpminstall@vitejs/plugin-vue-jsx-D安装完之后在vite.config.ts进行插件使用,代码如下......
  • 学习Vue3 第六章(认识Ref全家桶)
    ref接受一个内部值并返回一个响应式且可变的ref对象。ref对象仅有一个 .value property,指向该内部值<template><div><button@click="changeMsg">change</button><div>{{message}}</div></div></template><......
  • vue 配合后端请求异步加载APP.vue
    主要是想在加载路由什么的之前先请求一些配置参数,毕竟我的情况是首页要根据不同的配置显示不同的路由组件一般加载App.vue是这么写的import{createApp}from'vue'importAppfrom'./App.vue'createApp(App).mount('#app')异步加载的话,天才我深思熟虑后是这么写的。我......
  • Vite4+Typescript+Vue3+Pinia 从零搭建(6) - 状态管理pina
    项目代码同步至码云weiz-vue3-templatepina是vue3官方推荐的状态管理库,由Vue核心团队维护,旨在替代vuex。pina的更多介绍,可从pina官网查看特点更简洁直接的API,提供组合式风格的API支持模块热更新和服务端渲染对TS支持更为友好安装npmipinia使用1.创建......
  • uniapp+vue3 优惠券样式
    效果如图:template部分:<viewclass="item"><viewclass="box"><viewclass="content"><viewclass="head">优惠券</view><viewclass="content-box1">......
  • 【SpringBootWeb入门-2】请求响应-请求-Postman工具
    JavaWeb开发最常见的就是各类数据的请求以及响应,在讲解请求参数接收内容之前,我们先来介绍一款功能强大的接口测试工具:Postman。Postman介绍:一款功能强大的网页调试与发送网页HTTP请求的Chrome插件,作用:常用于进行接口测试。为什么要使用Postman?当前最为主流的开发模式是前后端分......
  • Vue3 实现网页水印
    一些公司和组织出于系统文件或信息安全保密的需要,需要在系统网页上增加带有个人标识的水印。首先我们来看这样一个水印功能的实现思路,通常是在我们原有的网页上附上一个DIV层,将它设置绝对定位铺满整个窗口,然后z-index值尽量往大了设,保证让水印层处于当前网页所有元素的上面,又不......
  • 关于vue如何在本地直接运行打包后的网页
    使用npmrunbuild可以对vue项目进行打包 生成文件夹dist,里面的html只能放在服务器查看效果,在本地打开会报错 这是因为vue-cli打包时,默认的publicPath路径是'/'只需要在vue.config.js文件中将publicPath路径改为 './' 或 ''  如果服务器上需要增加一层路径,也是......
  • 新建vue项目,并引入element ui和axios的步骤
    一、新建vue项目(1)win+R进入命令行 使用cmd (2)切换到需要创建vue项目的盘符下  直接D:就能切换到D盘 (3)使用vueui指令进入图形化创建vue项目的界面(注意在创建项目的时候,命令行不能关闭)  之后就在浏览器的界面中进行创建  点击下方的“在此创建新项目”(4)......
  • vue3 setup 父组件向子组件传递参数、方法|子组件向父组件传递数据,函数
    https://blog.csdn.net/qq_27517377/article/details/123163381https://blog.csdn.net/qq_27517377/article/details/123166367vue3setup父组件向子组件传递参数参数<template><el-rowclass="mb-4"> <el-buttontype="danger">props.vue传......