vue3——深入了解ref()
前言:自从入门vue3之后有一段时间之后能够比较熟练使用基本的api。然后就开始打算调试简单的api的源码,以此来加深对api的理解。
调试版本为3.2.45
ref()
接受一个内部值,返回一个响应式的、可更改的 ref 对象,此对象只有一个指向其内部值的属性 .value
<script setup>
import {ref} from 'vue'
const num = ref(0)//定义基本的数据类型
const numRef = ref(num)//定义已经是ref类型
const obj = ref({name:'zhangsan',age:12})//定义复杂数据类型
console.log("num", num);//用来快速定位需要调试的文件
</script>
设置断点
我们启动项目后,需要先找到需要调试的文件,然后设置断点。
开始调试
基础数据类型
接下来我们可以开始调试了,设置好断点后,只要重新刷新页面就可以进入调试界面。
想要详细了解控制台各个功能请点击此处
1.最开始的入口,会直接返回一个创建ref的函数,会携带两个参数,一个是所定义的值;一个是false,用于后面判断是否需要赋予ref各个相关的属性
function ref(value) {
return createRef(value, false);
}
2.通过isRef
函数来判断所定义的值是否已经是一个ref对象,如果是就直接返回该值。否则实例化RefImpl类,给该值赋予各个关于ref的属性,将其转化为一个ref对象
function createRef(rawValue, shallow) {
if (isRef(rawValue)) {
return rawValue;
}
return new RefImpl(rawValue, shallow);
}
function isRef(r) {
/*判断该值的__v_isRef属性是否为true,因为前面r=0,并没有所谓__v_isRef属性,所以这里r.__v_isRef是为undefined。这里的返回值是为false
*/
return !!(r && r.__v_isRef === true);
}
3.RefImpl
的构造器接收两个值,一个value
所定义的原始值,一个__v_isShallow
是否为浅层响应式
class RefImpl {
constructor(value, __v_isShallow) {
//是否为浅层响应式
this.__v_isShallow = __v_isShallow;
//dep 是一个 Set 类型的数据,它的作用是用来存储当前的 ref 值收集的依赖
this.dep = undefined;
//前面说到这个属性用来判断是否转化为ref对象
this.__v_isRef = true;
//用于保存当前 ref 值对应的原始值,当传递的参数是基本数据类型时,_value 和 _rawValue 相同。如果传递的参数是一个对象的话,_rawValue 用于保存转化前的原始值。
this._rawValue = __v_isShallow ? value : toRaw(value);
//用于保存 ref 当前的值,如果传递的参数是基本数据类型的话,_value 和 _rawValue 相同。如果传递的参数是一个对象的话,_value 就是经过 reactive 函数转化过的值。
this._value = __v_isShallow ? value : toReactive(value);
}
//当我们使用 .value 读取属性的时候,会执行 get value(){} 函数,并将函数的返回值作为 .value 读取到的值
get value() {
trackRefValue(this);
return this._value;
}
//在 set value() {} 函数中,判断新值和旧值有没有发生改变,如果有变化的是,则重新赋值 _rawValue 和 _value 属性,并触发当前 ref 值的依赖重新执行。
set value(newVal) {
//判断原始值是否为浅层响应式或是否为readonly对象
const useDirectValue = this.__v_isShallow || isShallow(newVal) || isReadonly(newVal);
//如果该值不是响应式或readonly对象,则返回原来的值,否则将该值通过toRaw转化为原来的target对象
newVal = useDirectValue ? newVal : toRaw(newVal);
//检测两个值是否相等,如果不同了就赋值为新的
if (hasChanged(newVal, this._rawValue)) {
this._rawValue = newVal;
//原始值如果为响应式则直接返回,否则将其转化为响应式
this._value = useDirectValue ? newVal : toReactive(newVal);
//触发依赖,更新视图
triggerRefValue(this, newVal);
}
}
}
关于get value(){}
的有关函数
function trackRefValue(ref) {
// 判断是否需要收集依赖
// shouldTrack 全局变量,代表当前是否需要 track 收集依赖
// activeEffect 全局变量,代表当前的副作用对象 ReactiveEffect
if (shouldTrack && activeEffect) {
ref = toRaw(ref);
// 如果没有 dep 属性,则初始化 dep,dep 是一个 Set<ReactiveEffect>,存储副作用函数
// trackEffects 收集依赖
if ((process.env.NODE_ENV !== 'production')) {
trackEffects(ref.dep || (ref.dep = createDep()), {
target: ref,
type: "get" /* TrackOpTypes.GET */,
key: 'value'
});
}
else {
trackEffects(ref.dep || (ref.dep = createDep()));
}
}
}
关于set value(){}
的有关函数,这里涉及很多函数调用,篇幅有点长,笔者就不一一列出来了,有兴趣的读者可以自己改变两个值,一个基本数据类型,一个复杂类型来作为断点进行调试。
function isShallow(value) {
return !!(value && value["__v_isShallow" /* ReactiveFlags.IS_SHALLOW */]);//判断原始值是否为浅层响应式
}
function isReadonly(value) {
return !!(value && value["__v_isReadonly" /* ReactiveFlags.IS_READONLY */]);//判断原始值是否为只读
}
4.经过RefImpl
类的实例化后,我们所定义的原始值赋值许多属性
ref类型
这次我们调试的是已经定义过的ref对象
const num = ref(0)
const numRef = ref(num)
console.log("numRef", numRef);
这里是在isRef
函数判断开始不同
从调试的截图可以看出,这里isRef()
返回是true,那么就会直接返回原始值。
复杂数据类型
const objRef = ref({name:'张三',age:18})
console.log('objRef',objRef)
这里调试所运行的函数跟基础类型前三步一样,当实例RefImpl类
的时候,执行下面这条语句的时候
this._value = __v_isShallow ? value : toReactive(value);
//从这一条语句开始不同
/*这条语句主要判断原始值是否为基础数据类型复杂数据类型。基础数据类型会直接返回原值。复杂数据类型会走reactive函数进行proxy代理*/
const toReactive = (value) => isObject(value) ? reactive(value) : value;
//判断是否原始值是否为object类型
const isObject = (val) => val !== null && typeof val === 'object';
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);
}
createReactiveObject
函数调用
/*createReactiveObject接收五个参数:
target被代理的对象,
isReadonl是不是只读的,
baseHandlers proxy的捕获器,
collectionHandlers针对集合的proxy捕获器,
proxyMap一个用于缓存proxy的`WeakMap`对象*/
function createReactiveObject(target, isReadonly, baseHandlers, collectionHandlers, proxyMap) {
//如果target不是对象则提示并返回
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;
}
//进行target代理操作
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
的实现,一般来说我们很少用到Map和Set类型数据(其实是mutableCollectionHandlers
复杂多了,调试好久,查阅了许多资料也没怎么搞懂o(╥﹏╥)o)
const mutableHandlers = {
get,// 获取值的拦截
set,// 更新值的拦截
deleteProperty,// 删除拦截
has,// 绑定访问对象时会拦截
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);
}
写到这对于ref的内部运行了解起码有了基本的认知。后续应该会出关于其他比较简单的api调试过程,比如reactive
。等水平上去后,准备调试理解依赖的收集以及触发加上虚拟DOM的算法。
总结:这次调试ref源码就到了。虽不完整,但是磕磕碰碰还是弄出大概了。希望这篇文章能对你有所帮助,如果可以请给个赞,谢谢大家(* ̄︶ ̄)。如果文章有写的不对的地方,请帮忙指出,促进笔者的成长(* ̄︶ ̄)
转载自:https://juejin.cn/post/7181673853502881848