在vue3的开发中,reactive是提供实现响应式数据的方法。日常开发这个是使用频率很高的api。这篇 文章 笔者就来 探索 其内部运行机制。 小白 一枚,写得不好请多多见谅。
调试版本为 3.2.45
什么是reactive?
reactive是Vue3中提供实现响应式数据的方法.
在Vue2中响应式数据是通过 define PR o PE rty 来实现的.
而在Vue3响应式数据是通过ES6的 Proxy 来实现的
reactive注意点
reactive参数必须是 对象 (json/arr)
如果给reactive传递了其他对象,默认情况下修改对象,界面不会自动更新,如果想更新,可以通过重新赋值的方式。
<script SETUP >
import {reactive} From 'vue'
const data = reactive({ //定义对象
n am e:'测试',
age:10
})
const num = reactive(1)//定义基本数据类型
console. LOG (data)//便于定位到调试位置
</script>
<template>
<div>
< h1 >{{ data.name }}</h1>
</div>
</template>
<style scoped></style>
设置断点
开始 调试
接下来我们可以开始调试了,设置好断点后,只要重新刷新页面就可以进入调试界面。
复杂数据类型
我们先调试 简单 的基本数据类型
/*1.初始进来函数,判断目标对象t arg et 是否 为只读对象,如果是直接返回*/
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 &am p; & 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" f rom strings like "[object RawType]"
return toTypeString(value).slice(8, -1);
};
const toTypeString = (value) => objectToString.call(value);
//3.这里rawType是为'Object'所以会返回1
function targetTypeMap(rawType) {
sw IT ch (rawType) {
case 'Object':
case 'Array':
return 1 /* TargetType .COM MON */;
case 'Map':
case 'Set':
case 'WeakMap':
case 'WeakSet':
return 2 /* TargetType.COLLECTION */;
default:
return 0 /* TargetType.INVALID */;//返回0说明除前面的类型外其他都不能被代理,如Date,RegE xp ,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.getOwnProperty names ()
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, rec ei ver) {
//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 test Click = ()=>{
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;
}
@H_ 512 _294@
因为判断类型不是对象,所以会在控制台打印出警告,并且直接返回原数据
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 函数的 读取操作 。
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对象,被 reactive api处理过就会有 __v_raw 私有属性,然后再进行 receiver 的判断,判断 target 是否为只读或浅层响应。如果都不是则从缓存proxy的 WeakMap 对象中获取该元素。最后直接返回 target 的原始数据(未被proxy代理过)。
最后回到之前的判断,由下图可知, target 的 __v_raw 属性存在, isReadonly 为false, __v_isReactive 的值为true,可以说明 reactive 函数需要处理的对象是一个被 reactive API处理过的对象,然后直接返回该对象的原始数据。
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,
for each : 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 的源码调试就到这了,这只是其中一小部分的源码,希望有兴趣的读者可以以此深入
到此这篇关于深入理解vue3中的reactive()的文章就介绍到这了,更多相关vue3 reactive()内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!
您可能感兴趣的文章: 关于vue3中的reactive赋值问题 vue3中的reactive函数声明数组方式 vue3中reactive数据被重新赋值后无法双向绑定的解决 vue3 关于reactive的重置问题及解决 vue3中reactive不能直接赋值的解决方案 对Vue3中reactive的深入理解 Vue3中ref与reactive的详解与扩展 详解Vue3中ref和reactive函数的使用
总结
以上是 为你收集整理的 深入理解vue3中的reactive() 全部内容,希望文章能够帮你解决 深入理解vue3中的reactive() 所遇到的问题。
如果觉得 网站内容还不错, 推荐好友。
查看更多关于深入理解vue3中的reactive()的详细内容...