In the development of vue3, reactive provides a method to implement responsive data. This is a frequently used API in daily development. In this article, the author will explore its internal operating mechanism. I'm a novice, so please forgive me for my poor writing.
The debug version is 3.2.45
What is reactive?
reactive is the implementation response provided in Vue3 Reactive data method.
In Vue2, reactive data is implemented through defineProperty.
And in Vue3, reactive data is implemented through ES6's Proxy To achieve
reactive attention points
reactive parameter must be an object(json/arr)
if Other objects are passed to reactive. By default, if the object is modified, the interface will not be automatically updated. If you want to update, you can reassign the value. [Related recommendations: vuejs video tutorial, web front-end development】
<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>
Next we can start debugging. After setting the breakpoint, just refresh the page to enter the debugging interface.
Let’s debug the simple basic data type first
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
Method call Process
//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等 } }
In the createReactiveObject
methodconst proxy = new Proxy(target, targetType === 2 /* TargetType.COLLECTION */ ? collectionHandlers : baseHandlers);
In this statement, the second parameter determines whether the target is a Map or Set type. Therefore, different handlers are used for dependency collection.
In the debugged file node_modules/@vue/reactivity/dist/reactivity.esm-bundler.js
, we createReactiveObject# from the reactive
function ##Two parameters of the function call mutableHandlers
and mutableCollectionHandlers
start to query the implementation of
<div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:js;toolbar:false;">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) ? &#39;length&#39; : ITERATE_KEY);
return Reflect.ownKeys(target);
}</pre><div class="contentsignin">Copy after login</div></div>
<div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:js;toolbar:false;">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;
};
}</pre><div class="contentsignin">Copy after login</div></div>
attribute, if it is true, it means target
is already a reactive object .
and __v_isShallow
attributes in sequence to determine whether it is a read-only and shallow response. If so, return the corresponding packaged target. .
attribute, here is the nesting of ternary, mainly to determine whether the original data is read-only
or shallow Respond to , and then look for the target object in the corresponding Map. If both are true, it means that the target is already a responsive object.
is an array, some methods need to be modified (for includes, indexOf
, lastIndexOf
, push
, pop
, shift
, unshift
, splice
) for special processing. And perform collection dependencies on each element of the array, and then obtain the value of the array function through Reflect.
Get the value.
, __proto__
, __v_isRef
, __isVue
, if it is to directly return the res
obtained previously, without subsequent processing;
object, target
If it is not an array, it will be automatically unpacked.
is Object
, perform deep responsive processing. It can be seen from here that Proxy
creates a responsive object lazily. Only when the corresponding key
is accessed will the responsive object continue to be created, otherwise there is no need to create it.
Example:<div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:js;toolbar:false;">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本身的属性那么说明执行的是&#39;add&#39;操作,增加属性
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;
};
}</pre><div class="contentsignin">Copy after login</div></div>
1 , Taking the code
as an example, the four parameters are:
: target object, that is, target= {"name": "Test", "age": 10}
(This is a normal object)
: The corresponding key to be modified, that is, key: "name"
: The modified value, that is, value: "2"
: The proxy for the target object. That isreceiver=Proxy {"name": "test","age": 10}
<p>2、<code>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; }
因为判断类型不是对象,所以会在控制台打印出警告,并且直接返回原数据
<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
:当前代理对象的源对象,即target3.在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对象,被reactive
api处理过就会有__v_raw
私有属性,然后再进行receiver
的判断,判断target
是否为只读或浅层响应。如果都不是则从缓存proxy的WeakMap
对象中获取该元素。最后直接返回target
的原始数据(未被proxy代理过)。
最后回到之前的判断,由下图可知,target
的__v_raw
属性存在,isReadonly
为false,__v_isReactive
的值为true,可以说明reactive
函数需要处理的对象是一个被reactive
API处理过的对象,然后直接返回该对象的原始数据。
经过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
类似,也是一组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 ]; }
后续比较复杂,加上笔者技术力还不够,如果想继续深入的读者,可以阅读这篇文章:Vue3响应式原理
总结:关于reactive
的源码调试就到这了,这只是其中一小部分的源码,希望有兴趣的读者可以以此深入,输出文章,共同进步成长。最后,如果这篇文章对你有所收获,请点个赞,如果有写的不对的地方,请大佬们指出(* ̄︶ ̄)。
The above is the detailed content of Let's talk in depth about reactive() in vue3. For more information, please follow other related articles on the PHP Chinese website!