前要
今日不知写何文,且逢群友学习vue3,那就写写vue3和vue2的区别以及vue3的特点吧。提到vue3和vue2的区别,大家首先会想到什么呢?是组合式API、数据响应式的不同、diff算法的不同、生命周期的不同还是setup的引入等等?今天就来讲讲vue3的响应式原理吧。
什么是数据响应式?为什么需要响应式数据?
什么是数据响应式,这是一个好问题,在了解vue2和vue3的响应式原理前,我们很有必要知道什么是数据响应式。首先我们来把数据响应式这个词分开 数据 + 响应式,数据我们知道,JS中常见的数据类型我们恐怕早就背的滚瓜烂熟了,那我们就来研究一下什么是响应式吧。
打开vue官网,右上角把中文调整为英文,看看官网是怎么称呼响应式的,没错是# Reactivity---反应,让我们暂时离开编程世界,来到现实世界,想一想什么是反应,通常什么时候才需要对某件东西有反应?好好想一想,大胆一点!无论是什么都可以,你总会对什么有反应的才对。你总会开心,伤心,难过,兴奋的吧,这些都是我们对外界环境的反应?什么?你还是不能理解这些和响应式有什么关系?你不知道你什么时候会开心,难过,伤心,但是你知道你看见苍老师就会兴奋,好吧,你算是无药可救了,但是好歹我们有一个合适的例子来了解什么是反应了。你对苍老师兴奋,为什么会兴奋?是苍老师的一些行为让你产生了一些反应,就像一块石头扔进水中,泛起了波澜。反应就是就是你的兴奋就是水中的波澜。
但是我觉得这种必须还是不够恰当,换个说法,小明高考考上了985,如果你是一个陌生人你会对他考上985这件事情有反应吗?当然不会,但是如果你和他有关系呢?比如你是他爸,你就会兴奋,你是他仇人,你会不爽。让我们回到编程世界,数据响应式也是如此,所谓的数据响应式就是数据改变带来的一连串反应,根据关系的不同,带来的反应也不同。举个例子,a的值由1变成了2,c = a*2; b= a-2;b与c和a的关系不同做出的反应不同,b由2变成了4,c由-1变成了0。a的值的变化就好比小明考上了985,b和c一个是他爸一个是他仇人,关系不同造成的反应也不同。
而在vue中,关系更多,watch、computed、数据绑定、vdom等等,vue会将一个响应式数据的关系收集起来,等响应式数据发生变化的时候,一个个进行通知进行各自的反应。为什么需要响应式数据呢?伟大的哲学家♂,南通天使投资人春哥说过如果没有响应式数据的话,a的值由1变成了2,即使是天王老子来了也不知道。我们需要有一个东西来通知和a有关系的事物进行相应的变化。
vue3 和 vue2 中的响应式有什么不同?
了解了什么是数据响应式,我们来了解一下vue2和vue3中的数据响应式有什么不同?要想知道两者有什么不同,首先我们得了解一下vue2和vue3中都是怎么实现数据响应式的。
数据响应式实现原理
<<Vue.js的设计与实现>>一文中提到过通过对拦截一个对象属性的读取和设置操作来实现响应式系统,在ES6之前,vue2使用defineProperty来实现拦截操作,到了ES6,vue3使用proxy代理来实现拦截操作。让我们来看看vue在拦截读取get和设置set的时候都做了些什么。
- get拦截
在dom节点,其他数据操作访问响应式数据时,都会传入一个副作用函数fn,fn中存储着需要这个响应式数据做什么,打个比方给一个dom传值。
// html
<div id="test">{{ a }}</div>
// js
let a = ref(0)
// 给一个dom传值,在div访问a的值的时候,这个操作就被当做了副作用函数,被记录了下来
// 对应的副作用函数 fn
let fn = () => {
document.getElementById('#test').innerText = a.value
}
解释了什么是副作用函数fn,我们来看看拿到了副作用函数后,vue干了些什么。vue会将这些副作用函数存入桶(bucket)中,关于桶是什么这里不作解释,它和栈队列一样是一种数据结构,在这里你只需要知道副作用函数被存入一个地方进行保存,等到需要的时候再取出来执行。
- set拦截
上面我们知道get拦截的时候vue做了些什么,就2点
1.获取副作用函数
2.将副作用函数存入桶中
是不是很简单,很好理解,就和把大象放入冰箱一样简单。set的拦截更加简单,简单来说就是把冰箱打开,把大象取出来。set拦截的时候,会把副作用函数取出来执行一遍。
// 续上文,将a的值由 1 变成 2
a.value = 2;
// set拦截的内部操作
// 假设将桶存储在a.effect中
a.effect.forEach(item => item())
// 此时 document.getElementById('#test').innerText = a.value 就会被再执行一次,dom的值也随之更新了。
当然vue的响应式系统不会这么简单,要不然人人都是鱿鱼嘻了。这里只做简单减少,后面还有根据更新的属性,执行不同的副作用等等优化操作。看到这里也只能是算是刚刚接触了什么是响应式系统。
vue3 和 vue2 响应式实现的不同
了解了什么是响应式原理以及简单的响应式原理实现过程,我们来看看在vue3和vue2中,这一块都是怎么实现的?在vue2中是通过defineProperty实现的,在vue3中主要是通过proxy代理实现的,有的人可能会看到vue3还有使用reflect配合proxy一起实现。这里我对于reflect作用的理解是修改this指向,便于更好的收集副作用函数(收集依赖)。
那两者响应式系统有什么不同呢?主要由以下几点:
- Proxy 代替 Object.defineProperty:Vue 3 使用了 JavaScript 的原生 Proxy 对象来实现响应式。相比于旧的 Object.defineProperty,Proxy 能够捕捉和拦截更多类型的操作,包括对象新增属性的访问、删除属性以及数组索引修改以及长度变化,从而实现更好的响应式效果。
- 嵌套属性的响应性追踪:Vue 3 可以追踪嵌套对象中的属性变化,并自动更新相关的视图。无需使用
$set方法来更新嵌套对象的属性。 - 减少了无效依赖的触发:Vue 3 在性能方面进行了一系列的优化,包括减少了触发无效依赖的次数、优化了内部追踪机制等,从而提高了整体的响应式性能。
ref和reactive函数:Vue 3 引入了ref和reactive这两个新的函数,用于创建响应式对象。用来显式的来创建响应式数据。而vue2是在option式写法中,data()中隐式的创建响应式数据。这里也解释了vue3为什么基本数据类型也需要使用ref来包装起来,而vue2只需要放在data()的return中就可以了,因为一个是显式创建,一个是隐式创建。
总结
此文介绍了什么是响应式系统,也介绍了vue中响应式实现的基本原理,分析了vue2和vue3中实现原理的不同,以及2者的区别。作者实力太弱,所以文章内容深度也不够,后面打算再介绍一些,vue3中 watch、computed的实现方法以及如何代理的非原始数据类型。