您好,欢迎访问一九零五行业门户网

深入聊聊vue3中的reactive()

在vue3的开发中,reactive是提供实现响应式数据的方法。日常开发这个是使用频率很高的api。这篇文章笔者就来探索其内部运行机制。小白一枚,写得不好请多多见谅。
调试版本为3.2.45
什么是reactive?
reactive是vue3中提供实现响应式数据的方法.
在vue2中响应式数据是通过defineproperty来实现的.
而在vue3响应式数据是通过es6的proxy来实现的
reactive注意点
reactive参数必须是对象(json/arr)
如果给reactive传递了其他对象,默认情况下修改对象,界面不会自动更新,如果想更新,可以通过重新赋值的方式。【相关推荐:vuejs视频教程、web前端开发】
<script setup>import {reactive} from 'vue'const data = reactive({ //定义对象 name:'测试', age:10})const num = reactive(1)//定义基本数据类型console.log(data)//便于定位到调试位置</script><template><div><h1>{{ data.name }}</h1></div></template><style scoped></style>
设置断点
开始调试接下来我们可以开始调试了,设置好断点后,只要重新刷新页面就可以进入调试界面。
复杂数据类型我们先调试简单的基本数据类型
1.
/*1.初始进来函数,判断目标对象target是否为只读对象,如果是直接返回*/function reactive(target) { // if trying to observe a readonly proxy, return the readonly version. if (isreadonly(target)) { return target; } //创建一个reactive对象,五个参数后续会讲解 return createreactiveobject(target, false, mutablehandlers, mutablecollectionhandlers, reactivemap);}/*2.判断是来判断target是否为只读。*/function isreadonly(value) { return !!(value && value["__v_isreadonly" /* reactiveflags.is_readonly */]);}/*3.创建一个reactive对象*//*createreactiveobject接收五个参数:target被代理的对象,isreadonl是不是只读的,basehandlers proxy的捕获器,collectionhandlers针对集合的proxy捕获器,proxymap一个用于缓存proxy的`weakmap`对象*/function createreactiveobject(target, isreadonly, basehandlers, collectionhandlers, proxymap) {//如果target不是对象则提示并返回/*这里会跳转到如下方法判断是否原始值是否为object类型 const isobject = (val) => val !== null && typeof val === 'object';*/ if (!isobject(target)) { if ((process.env.node_env !== 'production')) { console.warn(`value cannot be made reactive: ${string(target)}`); } return target; } // 如果target已经是proxy是代理对象则直接返回. if (target["__v_raw" /* reactiveflags.raw */] && !(isreadonly && target["__v_isreactive" /* reactiveflags.is_reactive */])) { return target; } // 从proxymap中获取缓存的proxy对象,如果存在的话,直接返回proxymap中对应的proxy。否则创建proxy。 const existingproxy = proxymap.get(target); if (existingproxy) { return existingproxy; } // 并不是任何对象都可以被proxy所代理。这里会通过gettargettype方法来进行判断。 const targettype = gettargettype(target); //当类型值判断出是不能代理的类型则直接返回 if (targettype === 0 /* targettype.invalid */) { return target; } //通过使用proxy函数劫持target对象,返回的结果即为响应式对象了。这里的处理函数会根据target对象不同而不同(这两个函数都是参数传入的): //object或者array的处理函数是collectionhandlers; //map,set,weakmap,weakset的处理函数是basehandlers; const proxy = new proxy(target, targettype === 2 /* targettype.collection */ ? collectionhandlers : basehandlers); proxymap.set(target, proxy); return proxy;}
gettargettype方法调用流程
//1.进入判断如果value有__v_skip属性且为true或对象是可拓展则返回0,否则走类型判断函数function gettargettype(value) {//object.isextensible() 方法判断一个对象是否是可扩展的(是否可以在它上面添加新的属性)。 return value["__v_skip" /* reactiveflags.skip */] || !object.isextensible(value) ? 0 /* targettype.invalid */ : targettypemap(torawtype(value));}//2.这里通过object.prototype.tostring.call(obj)来判断数据类型const torawtype = (value) => { // extract "rawtype" from strings like "[object rawtype]" return totypestring(value).slice(8, -1);};const totypestring = (value) => objecttostring.call(value);//3.这里rawtype是为'object'所以会返回1function targettypemap(rawtype) { switch (rawtype) { case 'object': case 'array': return 1 /* targettype.common */; case 'map': case 'set': case 'weakmap': case 'weakset': return 2 /* targettype.collection */; default: return 0 /* targettype.invalid */;//返回0说明除前面的类型外其他都不能被代理,如date,regexp,promise等 }}
在createreactiveobject方法中const proxy = new proxy(target, targettype === 2 /* targettype.collection */ ? collectionhandlers : basehandlers);这一条语句中,第二个参数判断target是否为map或者set类型。从而使用不同的handler来进行依赖收集。
在调试的文件node_modules/@vue/reactivity/dist/reactivity.esm-bundler.js中,我们从reactive函数的createreactiveobject函数调用的其中两个参数mutablehandlers和mutablecollectionhandlers开始往上查询
mutablehandlers的实现const mutablehandlers = { get,// 获取值的拦截,访问对象时会触发 set,// 更新值的拦截,设置对象属性会触发 deleteproperty,// 删除拦截,删除对象属性会触发 has,// 绑定访问对象时会拦截,in操作符会触发 ownkeys// 获取属性key列表};function deleteproperty(target, key) { // key是否是target自身的属性 const hadkey = hasown(target, key); // 旧值 const oldvalue = target[key]; // 调用reflect.deleteproperty从target上删除属性 const result = reflect.deleteproperty(target, key); // 如果删除成功并且target自身有key,则触发依赖 if (result && hadkey) { trigger(target, "delete" /* triggeroptypes.delete */, key, undefined, oldvalue); } return result;}//function has(target, key) { //检查目标对象是否存在此属性。 const result = reflect.has(target, key); // key不是symbol类型或不是symbol的内置属性,进行依赖收集 if (!issymbol(key) || !builtinsymbols.has(key)) { track(target, "has" /* trackoptypes.has */, key); } return result;}/*ownkeys可以拦截以下操作:1.object.keys()2.object.getownpropertynames()3.object.getownpropertysymbols()4.reflect.ownkeys()操作*/function ownkeys(target) { track(target, "iterate" /* trackoptypes.iterate */, isarray(target) ? 'length' : iterate_key); return reflect.ownkeys(target);}
get方法实现const get = /*#__pure__*/ creategetter();/*传递两个参数默认都为falseisreadonly是否为只读shallow是否转换为浅层响应,即reactive---> shallowreactive,shallowreactive监听了第一层属性的值,一旦发生改变,则更新视图;其他层,虽然值发生了改变,但是视图不会进行更新*/function creategetter(isreadonly = false, shallow = false) { return function get(target, key, receiver) { //1.是否已被reactive相关api处理过; if (key === "__v_isreactive" /* reactiveflags.is_reactive */) { return !isreadonly; } //2.是否被readonly相关api处理过 else if (key === "__v_isreadonly" /* reactiveflags.is_readonly */) { return isreadonly; } else if (key === "__v_isshallow" /* reactiveflags.is_shallow */) { return shallow; } //3.检测__v_raw属性 else if (key === "__v_raw" /* reactiveflags.raw */ && receiver === (isreadonly ? shallow ? shallowreadonlymap : readonlymap : shallow ? shallowreactivemap : reactivemap).get(target)) { return target; } //4.如果target是数组,且命中了一些属性,则执行函数方法 const targetisarray = isarray(target); if (!isreadonly && targetisarray && hasown(arrayinstrumentations, key)) { return reflect.get(arrayinstrumentations, key, receiver); } //5.reflect获取值 const res = reflect.get(target, key, receiver); //6.判断是否为特殊的属性值 if (issymbol(key) ? builtinsymbols.has(key) : isnontrackablekeys(key)) { return res; } if (!isreadonly) { track(target, "get" /* trackoptypes.get */, key); } if (shallow) { return res; } //7.判断是否为ref对象 if (isref(res)) { // ref unwrapping - skip unwrap for array + integer key. return targetisarray && isintegerkey(key) ? res : res.value; } //8.判断是否为对象 if (isobject(res)) { // convert returned value into a proxy as well. we do the isobject check // here to avoid invalid value warning. also need to lazy access readonly // and reactive here to avoid circular dependency. return isreadonly ? readonly(res) : reactive(res); } return res; };}
检测__v_isreactive属性,如果为true,表示target已经是一个响应式对象了。
依次检测__v_isreadonly和__v_isshallow属性,判断是否为只读和浅层响应,如果是则返回对应包装过的target。
检测__v_raw属性,这里是三元的嵌套,主要判断原始数据是否为只读或者浅层响应,然后在对应的map里面寻找是否有该目标对象,如果都为true则说明target已经为响应式对象。
如果target是数组,需要对一些方法(针对includes、indexof、lastindexof、push、pop、shift、unshift、splice)进行特殊处理。并对数组的每个元素执行收集依赖,然后通过reflect获取数组函数的值。
reflect获取值。
判断是否为特殊的属性值,symbol, __proto__,__v_isref,__isvue, 如果是直接返回前面得到的res,不做后续处理;
如果为ref对象,target不是数组的情况下,会自动解包。
如果res是object,进行深层响应式处理。从这里就能看出,proxy是懒惰式的创建响应式对象,只有访问对应的key,才会继续创建响应式对象,否则不用创建。
set方法实现例子:data.name='2'
const set = /*#__pure__*/ createsetter(); //shallow是否转换为浅层响应,默认为falsefunction createsetter(shallow = false) { //1.传递四个参数 return function set(target, key, value, receiver) { let oldvalue = target[key]; //首先获取旧值,如果旧值是ref类型,且新值不是ref类型,则不允许修改 if (isreadonly(oldvalue) && isref(oldvalue) && !isref(value)) { return false; } //2.根据传递的shallow参数,来执行之后的操作 if (!shallow) { if (!isshallow(value) && !isreadonly(value)) { oldvalue = toraw(oldvalue); value = toraw(value); } if (!isarray(target) && isref(oldvalue) && !isref(value)) { oldvalue.value = value; return true; } } //3.检测key是不是target本身的属性 const hadkey = isarray(target) && isintegerkey(key) ? number(key) < target.length : hasown(target, key); //利用reflect.set()来修改值,返回一个boolean值表明是否成功设置属性 //reflect.set(设置属性的目标对象, 设置的属性的名称, 设置的值, 如果遇到 `setter`,`receiver`则为`setter`调用时的`this`值) const result = reflect.set(target, key, value, receiver); // 如果目标是原始原型链中的某个元素,则不要触发 if (target === toraw(receiver)) { //如果不是target本身的属性那么说明执行的是'add'操作,增加属性 if (!hadkey) { trigger(target, "add" /* triggeroptypes.add */, key, value); } //4.比较新旧值,是否触发依赖 else if (haschanged(value, oldvalue)) { //5.触发依赖 trigger(target, "set" /* triggeroptypes.set */, key, value, oldvalue); } } return result; };}
1、以data.name='2'这段代码为例,四个参数分别为:
target:目标对象,即target={"name": "测试","age": 10}(此处为普通对象)
key:修改的对应key,即key: "name"
value:修改的值,即value: "2"
receiver:目标对象的代理。即receiver=proxy {"name": "测试","age": 10}
2、shallow为false的时候。
第一个判断:如果新值不是浅层响应式并且不是readonly,新旧值取其对应的原始值。
第二个判断:如果target不是数组并且旧值是ref类型,新值不是ref类型,直接修改oldvalue.value为value
3.检测key是不是target本身的属性。这里的hadkey有两个方法,isarray就不解释,就是判断是否为数组
isintegerkey:判断是不是数字型的字符串key值
//判断参数是否为string类型,是则返回trueconst isstring = (val) => typeof val === 'string';//如果参数是string类型并且不是'nan',且排除-值(排除负数),然后将 key 转换成数字再隐式转换为字符串,与原 key 对比const isintegerkey = (key) => isstring(key) && key !== 'nan' && key[0] !== '-' && '' + parseint(key, 10) === key;
4.比较新旧值,如果新旧值不同,则触发依赖进行更新
haschanged方法
//object.is()方法判断两个值是否是相同的值。const haschanged = (value, oldvalue) => !object.is(value, oldvalue);
5.触发依赖,这里太过复杂,笔者也没搞懂,如果有兴趣的读者可自行去调试
<script setup>import { reactive } from "vue";const data = reactive({ name: "测试", age: 10,});data.name='1'//这里并未收集依赖,在处理完 createsetupcontext 的上下文后,组件会停止依赖收集,并且开始执行 setup 函数。具体原因有兴趣的读者可以自行去了解const testclick = ()=>{ data.name='test'}</script><template> <div> <h1>{{ data.name }}</h1> <el-button @click="testclick">click</el-button> </div></template><style scoped></style>
基本数据类型const num = reactive(2)
这里比较简单,在createreactiveobject函数方法里面:
if (!isobject(target)) { if ((process.env.node_env !== 'production')) { console.warn(`value cannot be made reactive: ${string(target)}`); } return target; }
因为判断类型不是对象,所以会在控制台打印出警告,并且直接返回原数据
proxy对象<script>const data = reactive({ name: "测试", age: 10,});const num = reactive(data)//定义一个已经是响应式对象</script>
1.调试开始进来reactive函数,然后会经过isreadonly函数,这里跟前面不同的是,target是一个proxy对象,它已经被代理过有set,get等handler。所以在isreadonly函数读取target的时候,target会进行get函数的读取操作。
function reactive(target) { // if trying to observe a readonly proxy, return the readonly version. if (isreadonly(target)) { return target; } return createreactiveobject(target, false, mutablehandlers, mutablecollectionhandlers, reactivemap);}
2.可以看到get传入的参数有个key="__v_isreadonly",这里的isreadonly返回是false,接下来进入createreactiveobject函数
这里说明下,在本次调试中常见的vue里面定义的私有属性有:
__v_skip:是否无效标识,用于跳过监听__v_isreactive:是否已被reactive相关api处理过__v_isreadonly:是否被readonly相关api处理过__v_isshallow:是否为浅层响应式对象__v_raw:当前代理对象的源对象,即target
3.在createreactiveobject函数中,经过target["__v_isreactive"]的时候会触发target的get函数,这时候get函数传入的参数中key='__v_raw'
if (target["__v_raw" /* reactiveflags.raw */] && !(isreadonly && target["__v_isreactive" /* reactiveflags.is_reactive */])) { return target; }
由上图可知我们检测target即已定义过的proxy对象,被reactiveapi处理过就会有__v_raw私有属性,然后再进行receiver的判断,判断target是否为只读或浅层响应。如果都不是则从缓存proxy的weakmap对象中获取该元素。最后直接返回target的原始数据(未被proxy代理过)。
最后回到之前的判断,由下图可知,target的__v_raw属性存在,isreadonly为false,__v_isreactive的值为true,可以说明reactive函数需要处理的对象是一个被reactiveapi处理过的对象,然后直接返回该对象的原始数据。
ref类型经过ref函数处理,其本质也是一个对象,所以使用reactive函数处理ref类型就跟处理复杂数据类型一样过程。对于ref函数,如果大家有兴趣可以阅读这篇文章vue3——深入了解ref()。有些内容跟这里差不多,也有对此补充,如果觉得不错请各位帮忙点个赞
(开发中应该不会有这种嵌套行为吧,这里只是为了测试多样化)。
<script setup>import { reactive,ref } from "vue";const data = reactive({ name: "测试", age: 10,});const numref = ref(1)const dataref = ref({ name: "测试2", age: 20,})const num = reactive(numref)const datareactive = reactive(dataref)console.log('data',data)console.log('numref',numref)console.log('num',num)console.log('dataref',dataref)console.log('datareactive',datareactive)</script>
map类型和set类型map 类型是键值对的有序列表,而键和值都可以是任意类型。set和map类似,也是一组key的集合,但不存储value。由于key不能重复,所以,在set中,没有重复的key。<script setup>import { reactive } from "vue";const mapdata = new map();mapdata.set('name','张三')const setdata = new set([1,2,3,1,1])console.log(mapdata)console.log(setdata)const mapreactive = reactive(mapdata)console.log(mapreactive)</script>
由上图可知map结构和set结构使用typeof判断是object,所有流程前面会跟复杂数据类型一样,知道在createreactiveobject函数的gettargettype()函数开始不同。
在gettargettype函数里面torawtype()判断数据类型所用方法为object.prototype.tostring.call()
const targettype = gettargettype(target);function gettargettype(value) { return value["__v_skip" /* reactiveflags.skip */] || !object.isextensible(value) ? 0 /* targettype.invalid */ : targettypemap(torawtype(value));}function targettypemap(rawtype) {//rawtype="map",这里返回值为2 switch (rawtype) { case 'object': case 'array': return 1 /* targettype.common */; case 'map': case 'set': case 'weakmap': case 'weakset': return 2 /* targettype.collection */; default: return 0 /* targettype.invalid */; }}
这时候targettype=2,在createreactiveobject的函数中const proxy = new proxy(target, targettype === 2 /* targettype.collection */ ? collectionhandlers : basehandlers);的三元表达式中可得知,这里的handler为collectionhandlers。
网上查找可在reactive函数中return createreactiveobject(target, false, mutablehandlers, mutablecollectionhandlers, reactivemap);这条语句找到,当rawtype=1时handler是用mutablehandlers,rawtype=1时是用mutablecollectionhandlers。
mutablecollectionhandlers方法:
const mutablecollectionhandlers = { get: /*#__pure__*/ createinstrumentationgetter(false, false)};//解构createinstrumentationsconst [mutableinstrumentations, readonlyinstrumentations, shallowinstrumentations, shallowreadonlyinstrumentations] = /* #__pure__*/ createinstrumentations();//传入两个参数,是否为可读,是否为浅层响应function createinstrumentationgetter(isreadonly, shallow) { const instrumentations = shallow ? isreadonly ? shallowreadonlyinstrumentations : shallowinstrumentations : isreadonly ? readonlyinstrumentations : mutableinstrumentations; return (target, key, receiver) => { if (key === "__v_isreactive" /* reactiveflags.is_reactive */) { return !isreadonly; } else if (key === "__v_isreadonly" /* reactiveflags.is_readonly */) { return isreadonly; } else if (key === "__v_raw" /* reactiveflags.raw */) { return target; } return reflect.get(hasown(instrumentations, key) && key in target ? instrumentations : target, key, receiver); };}
//篇幅问题以及这方面笔者并未深入,所以就大概带过function createinstrumentations() {//创建了四个对象,对象内部有很多方法,其他去掉了,完整可自行去调试查看 const mutableinstrumentations = { get(key) { return get$1(this, key); }, get size() { return size(this); }, has: has$1, add, set: set$1, delete: deleteentry, clear, foreach: createforeach(false, false) }; ................. //通过createiterablemethod方法操作keys、values、entries、symbol.iterator迭代器方法 const iteratormethods = ['keys', 'values', 'entries', symbol.iterator]; iteratormethods.foreach(method => { mutableinstrumentations[method] = createiterablemethod(method, false, false); readonlyinstrumentations[method] = createiterablemethod(method, true, false); shallowinstrumentations[method] = createiterablemethod(method, false, true); shallowreadonlyinstrumentations[method] = createiterablemethod(method, true, true); }); return [ mutableinstrumentations, readonlyinstrumentations, shallowinstrumentations, shallowreadonlyinstrumentations ];}
后续比较复杂,加上笔者技术力还不够,如果想继续深入的读者,可以阅读这篇文章:vue3响应式原理
总结:关于reactive的源码调试就到这了,这只是其中一小部分的源码,希望有兴趣的读者可以以此深入,输出文章,共同进步成长。最后,如果这篇文章对你有所收获,请点个赞,如果有写的不对的地方,请大佬们指出(* ̄︶ ̄)。
(学习视频分享:vuejs入门教程、编程基础视频)
以上就是深入聊聊vue3中的reactive()的详细内容。
其它类似信息

推荐信息