首页IT科技vue3.0 composition api(深入聊一聊vue3中的reactive())

vue3.0 composition api(深入聊一聊vue3中的reactive())

时间2025-09-09 19:21:41分类IT科技浏览5456
导读:在vue3的开发中,reactive是提供实现响应式数据的方法。日常开发这个是使用频率很高的api。这篇文章笔者就来探索其内部运行机制。小白一枚,写得不好请多多见谅。...

在vue3的开发中                ,reactive是提供实现响应式数据的方法               。日常开发这个是使用频率很高的api                       。这篇文章笔者就来探索其内部运行机制        。小白一枚                       ,写得不好请多多见谅               。

调试版本为3.2.45

什么是reactive?

reactive是Vue3中提供实现响应式数据的方法.

在Vue2中响应式数据是通过defineProperty来实现的.

而在Vue3响应式数据是通过ES6的Proxy来实现的

reactive注意点

reactive参数必须是对象(json/arr)

如果给reactive传递了其他对象,默认情况下修改对象,界面不会自动更新,如果想更新,可以通过重新赋值的方式                       。

<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.初始进来函数                       ,判断目标对象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所以会返回1 function 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(); /*传递两个参数默认都为false isReadonly是否为只读 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是否转换为浅层响应                       ,默认为false function 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类型                ,是则返回true const 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类型就跟处理复杂数据类型一样过程        。有些内容跟这里差不多       ,也有对此补充                ,如果觉得不错请各位帮忙点个赞

(开发中应该不会有这种嵌套行为吧                       ,这里只是为了测试多样化)       。

<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) }; //解构createInstrumentations const [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     ]; }

后续比较复杂       ,加上笔者技术力还不够                ,暂时先到这里吧

总结:关于reactive的源码调试就到这了                       ,这只是其中一小部分的源码       ,希望有兴趣的读者可以以此深入        ,输出文章                       ,共同进步成长                      。最后               ,如果这篇文章对你有所收获        ,请点个赞                       ,如果有写的不对的地方               ,请大神们指出                       。

创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!

展开全文READ MORE
教师教育管理平台登录账号(后台管理系统 – 页面布局设计)