Vue3源码【一】

您所在的位置:网站首页 响应式数据原理 Vue3源码【一】

Vue3源码【一】

2024-06-26 07:18:38| 来源: 网络整理| 查看: 265

响应式 ref、reactive

源码地址:https://github.com/vuejs/core

首先还是从最开始学的ref的源码看起,他的路径在packages/reactivity/src/ref.ts,这里看源码分析就直接将源码执行的步骤给他粘贴出来了哈。首先我们看一下ref是怎么创建的

1、创建Ref // 第一步,我们还是直接到ref关键字,可以看到这个,这个就是我们使用的ref()用来创建响应式对象的关键。他会去调用createRef,并且第二个指定了false export function ref(value?: unknown) { return createRef(value, false); } // 第二步,顺着上面往下执行,会调用createRef,这个时候我们知道第二个参数false是指什么了,也就是shallow,这个时候可能会想到shallowRef? // shallowRef:只处理基本数据类型的响应式, 不进行对象的响应式处理。那默认创建的这个ref指定了false,那要是shallowRef调用createRef创建Ref是不是就是指定的true呢?这个我们后面再看 // 在这里先判断isRef,这个很好理解,就是看入参的是不是一个ref了, function createRef(rawValue: unknown, shallow: boolean) { if (isRef(rawValue)) { return rawValue; } return new RefImpl(rawValue, shallow); } // 第三步,直接看RefImpl,这个就是将一个变量给包装成Ref(响应式对象) // 这里看构造函数,先都会判断一下shallow是真还是假,响应式对象入的是false,他数据包装会变成toRaw和toReactive // 在这里我们知道reactive是用来包对象类型的,这里ref创建本质上也是对调reactive的方法,同时我们也知道了为什么使用ref包的对象要加一个.value取取值赋值 // 看一下shallowRef的构造,果然就是return createRef(value, true),这样也解释了shallowRef为什么处理的是基本数据类型 // 看一下isRef方法,return !!(r && r.__v_isRef === true) r就是RefImpl实例对象,用来判断的也就是这个r.__v_isRef === true 是否为ref class RefImpl { private _value: T; private _rawValue: T; public dep?: Dep = undefined; public readonly; __v_isRef = true; constructor( value: T, public readonly __v_isShallow: boolean ) { this._rawValue = __v_isShallow ? value : toRaw(value); this._value = __v_isShallow ? value : toReactive(value); } get value() { trackRefValue(this); return this._value; } set value(newVal) { const useDirectValue = this.__v_isShallow || isShallow(newVal) || isReadonly(newVal); newVal = useDirectValue ? newVal : toRaw(newVal); if (hasChanged(newVal, this._rawValue)) { this._rawValue = newVal; this._value = useDirectValue ? newVal : toReactive(newVal); triggerRefValue(this, DirtyLevels.Dirty, newVal); } } } 2、依赖收集

我们先看一个他的get value是这么拿到值的,也就是trackRefValue方法。首先他在处理的时候先通过toRaw转成原始对象,从这里往下的源码就做了一些删减,有一些对数据进行异常判断处理的这里就都不展示了,主要看执行逻辑

export function trackRefValue(ref: RefBase) { // true && undefined if (shouldTrack && activeEffect) { // 先转成原始对象 ref = toRaw(ref) trackEffect( activeEffect, // ref.dep 不存在就调用createDep赋值给ref.dep 本质上是一个Map (ref.dep ??= createDep( () => (ref.dep = undefined), ref instanceof ComputedRefImpl ? ref : undefined, )), void 0, ) } }

重要的还是这个trackEffect方法。简单来说就是将里面的所有属性都给收集到一个map当中,通过这个map来做统一的依赖控制。后面取值也会从Map当中取值。

export function trackEffect( effect: ReactiveEffect, dep: Dep, debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { // 默认值 eff._trackId = 0 if (dep.get(effect) !== effect._trackId) { dep.set(effect, effect._trackId); // 默认值 effect._depsLength = 0 const oldDep = effect.deps[effect._depsLength]; if (oldDep !== dep) { if (oldDep) { cleanupDepEffect(oldDep, effect); } // effect.deps 本质上还是一个Map对象,在这里将所有的依赖收集起来 effect.deps[effect._depsLength++] = dep; } else { effect._depsLength++; } } } 3、依赖触发

这里从set重新设置值的时候开始执行,核心方法在triggerEffects。进来线通过遍历所有的依赖,找到我们需要修改的依赖的值,然后重新赋值,执行effect.trigger() ,到这里完成了依赖的触发。同时可以看一下这个effect是个什么,在这里面会接收一个匿名函数fn,并且在这里他是回去走run方法的。他的本质还是ReactiveEffect类的实例,他的run方法就是实例的run。最后回去执行那个匿名函数fn,也就是更改视图的方法 document.querySelector('#xxx').innerHTML = xxx 。到这里就完成了依赖值的更改以及视图的实时响应

export function triggerEffects( dep: Dep, dirtyLevel: DirtyLevels, debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { // pauseScheduleStack++ pauseScheduling(); // 遍历所有收集的依赖 for (const effect of dep.keys()) { // dep.get(effect) is very expensive, we need to calculate it lazily and reuse the result let tracking: boolean | undefined; // 依赖的_dirtyLevel < 4 && dep.get(effect) === effect._trackId) if ( effect._dirtyLevel queueEffectSchedulers.push(effect.scheduler); } } resetScheduling(); } export function effect( fn: () => T, options?: ReactiveEffectOptions ): ReactiveEffectRunner { if ((fn as ReactiveEffectRunner).effect instanceof ReactiveEffect) { fn = (fn as ReactiveEffectRunner).effect.fn; } const _effect = new ReactiveEffect(fn, NOOP, () => { if (_effect.dirty) { _effect.run(); } }); if (options) { extend(_effect, options); if (options.scope) recordEffectScope(_effect, options.scope); } if (!options || !options.lazy) { _effect.run(); } const runner = _effect.run.bind(_effect) as ReactiveEffectRunner; runner.effect = _effect; return runner; } 4、响应式扩展 4.1、isRef、shallowRef、toRaw、markRaw

这个的源码在1.1创建Ref的里面大概说了,就跳过了。补充一下toRaw和markRaw

isRef:是否为一个Ref

shallowRef:只处理基本数据类型的响应式, 不进行对象的响应式处理。

toRaw:将响应式对象转换成原始对象

markRaw:标记一个对象,使其永远不会再成为响应式对象

// 通过observed尝试获取其原始对象,如果可以找到再递归对象的键的值再进行转换,把代理对象下的每一个值都转成原始对象 export function toRaw(observed: T): T { // const raw = observed && (observed as Target)['__v_raw'] const raw = observed && (observed as Target)[ReactiveFlags.RAW]; return raw ? toRaw(raw) : observed; } // 在markRaw实现是给对象添加了__v_skip属性,从这保证不会触发依赖收集和触发依赖 export function markRaw(value: T): Raw { if (Object.isExtensible(value)) { def(value, ReactiveFlags.SKIP, true); } return value; } export const def = ( obj: object, key: string | symbol, value: any, writable = false ) => { Object.defineProperty(obj, key, { configurable: true, enumerable: false, writable, value }); }; 4.2、triggerRef

使用triggerRef可以强制更新页面DOM。这是因为我们创建了这个triggerRef,他会去调用triggerEffects ,那也就是1.3的依赖触发,依赖出发后会执行回调去更新页面。同样的因为shallowRef他没有去转成toReactive() ,那么他也就不会去做依赖收集和依赖触发的操作

export function triggerRefValue( ref: RefBase, dirtyLevel: DirtyLevels = DirtyLevels.Dirty, // 4 newVal?: any, ) { ref = toRaw(ref) const dep = ref.dep if (dep) { triggerEffects( dep, dirtyLevel, __DEV__ ? { target: ref, type: TriggerOpTypes.SET, key: 'value', newValue: newVal, } : void 0, ) } } 4.3、customRef

自定义ref,它是个工厂函数要求我们返回一个对象 并且实现 get 和 set 适合去做防抖之类的。这个的源码实现和Ref的区别就在于这里没有实现get和set的依赖收集和触发,需要手动实现。

export type CustomRefFactory = ( track: () => void, trigger: () => void ) => { get: () => T set: (value: T) => void } class CustomRefImpl { public dep?: Dep = undefined; private readonly _get: ReturnType['get']; private readonly _set: ReturnType['set']; public readonly __v_isRef = true; constructor(factory: CustomRefFactory) { const {get, set} = factory( () => trackRefValue(this), () => triggerRefValue(this) ); this._get = get; this._set = set; } get value() { return this._get(); } set value(newVal) { this._set(newVal); } } 4.4、toRef

创建一个ref对象,其value值指向另一个对象中的某个属性。先看一下它怎么用的

const obj = { a: 1, b: 2 }; // 把obj对象当中的a拿出来重新创建一个ref对象 ==> const a = ref(1) const a = toRef(obj, 'a'); setTimeout(() => { obj.a = 2; obj.b = 3; // 在这里修改了obj.a的值,a.value的值也会跟踪发生变化,但是DOM元素不会发生变化 // 原因在于obj对象不是响应式的,那么a也不会更新视图。反之如果obj是响应式的,那么a.value的值也会更新视图 console.log(' =====', obj, a); }, 2000);

源码实现(简化一下):

先找到toRef的实现,这里面有3种情况,第一种是source是ref,第二种是source是函数,第三种是source是对象,并且key存在,那么就返回propertyToRefpropertyToRef:判断对象的key的值是不是ref,如果是就返回,不是就返回propertyToRefpropertyToRef:把对象key的值做一个依赖收集 export function toRef(source, key, defaultValue): Ref { // 如果source是一个Ref直接返回...这个我就删了, // 如果是一个函数,就拿函数返回值 if (isFunction(source)) { return new GetterRefImpl(source) as any; } else if (isObject(source) && arguments.length > 1) { // 主要还是在这,入参是一个对象,并且有key return propertyToRef(source, key!, defaultValue); } else { return ref(source); } } // 看对象值是不是Ref直接返回,不然再实例化一个ObjectRefImpl function propertyToRef(source, key, defaultValue) { const val = source[key]; return isRef(val) ? val : (new ObjectRefImpl(source, key, defaultValue) as any); } // 在这里没有对get、set方法做依赖收集和触发,所以toRef包装后的对象的响应式跟_object是否是一个响应式对象相关 class ObjectRefImpl { public readonly __v_isRef = true; // 构造接收传递过来的对象,key,默认值 // constructor(_object,_key,_defaultValue) get value() { return val === undefined ? this._defaultValue! : this._object[this._key]; } set value(newVal) { this._object[this._key] = newVal; } } 4.5、toRefs

toRefs的实现:接收入参的一个object,遍历这个object,然后将每个值都用toRef包一层

export function toRefs(object: T): ToRefs { const ret: any = isArray(object) ? new Array(object.length) : {} for (const key in object) { ret[key] = propertyToRef(object, key) } return ret } 4.6、Reactive&shallowReactive&readonly 对于reactive来说他的响应式其实就是上面ref针对于对象那一块的响应式实现。而shallowReactive就是在创建reactive的时候传递的是mutableHandlers还是shallowReactiveHandlers, 就是在创建MutableReactiveHandler实例的时候是否将_isShallow指定为了true,默认值为falsereadonly在创建reactive时指定了_readonly,当值为true时会直接返回当前对象 5、简单实现reactive响应式

这里还是直接在vue的模版当中写了哈,就不用vue当中的ref、reactive什么的,从头实现一遍。

5.1、创建myReactive

首先我们直接创建一个myReactive变量指向一个回调函数,直接返回一个代理对象,首先看get方法,获取里面的key对应的值,我们把所有的key都放到track方法当中,也就是依赖收集,这个方法我们下一步再实现,同时我们判断对象的属性值是不是还是一个对象,如果还是我们就再用myReactive包一层,这样也就实现了深层监听。

然后是set方法。这里主要就是实现trigger依赖触发方法

const isObject = (target: any) => target !== null && typeof target === 'object'; const myReactive: any = (target: T) => { return new Proxy(target, { get(target, key, receiver) { const result = Reflect.get(target, key, receiver) as object; track(target, key); if (isObject(result)) { return myReactive(result); } return result; }, set(target, key, value, receiver) { const result = Reflect.set(target, key, value, receiver); trigger(target, key); return result; } }); }; 5.2、effect副作用函数

先实现一个effect,也就是一个对象去绑定一个对应的更新DOM的方法,当数据改变之后调用这个传递给effect的匿名函数去更新DOM

let activeEffect: () => void; const effect = (fn: Function) => { const _effect = () => { activeEffect = _effect; fn(); }; _effect(); console.log(' =====', activeEffect); }; 5.3、get方法:依赖收集, 创建一个名为targetMap的WeakMap实例。(WeakMap是一种特殊的Map,它的键名所指向的对象,不计入垃圾回收机制)方法入参:target(目标对象)和key(属性名)。这个函数用于追踪目标对象的属性与副作用函数(effect)之间的关系首先尝试从targetMap中获取depsMap(依赖映射),如果没有找到,则创建一个新的Map实例并将其与目标对象关联尝试从depsMap中获取deps(依赖集合),如果没有找到,则创建一个新的Set实例并将其与属性名关联最后,将当前的副作用函数(activeEffect)添加到deps集合中。这样,当目标对象的属性发生变化时,可以触发与之相关的副作用函数 const targetMap = new WeakMap(); const track = (target: object, key: any) => { let depsMap = targetMap.get(target); if (!depsMap) { depsMap = new Map(); targetMap.set(target, depsMap); } let deps = depsMap.get(key); if (!deps) { deps = new Set(); depsMap.set(key, deps); } deps.add(activeEffect); }; 5.4、set方法:依赖触发

这里就简单说明一下,在前面get方法已经做好了依赖收集操作,所以当对对象属性重新赋值的时候会触发trigger,会先从targetMap找当前对象,并且发现该对象存在targetMap当中(也就是说这个是一个响应式对象),再会去depsMap寻找key(重新赋值的key)对应副作用函数,然后通过副作用函数更新DOM就完成了响应式。

const trigger = (target: object, key: any) => { const depsMap = targetMap.get(target); if (!depsMap) return; const deps = depsMap.get(key); if (!deps) return; deps.forEach((effect: Function) => { effect(); }); }; 5.5、测试

直接放到vue的模版里面测试,其中myReactive、effect、track、trigger方法从上面拿下来即可,在这里测试只需要定义好effect函数,并且指定匿名函数去修改DOM元素,然后其他的使用myReactive和reactive是一样的。

const user = myReactive({ name: '张三', age: 18, foo: { bar: { a: 1 } } }); onMounted(() => { effect(() => { const demo = document.querySelector('#demo'); if (demo) { demo.innerHTML = user.name + '-----' + user.age + '-----' + user.foo.bar.a; } }); }); const change = () => { user.name = '李四'; setInterval(() => { user.foo.bar.a = Math.random() * 100; user.age++; }, 2000); }; change


【本文地址】

公司简介

联系我们

今日新闻


点击排行

实验室常用的仪器、试剂和
说到实验室常用到的东西,主要就分为仪器、试剂和耗
不用再找了,全球10大实验
01、赛默飞世尔科技(热电)Thermo Fisher Scientif
三代水柜的量产巅峰T-72坦
作者:寞寒最近,西边闹腾挺大,本来小寞以为忙完这
通风柜跟实验室通风系统有
说到通风柜跟实验室通风,不少人都纠结二者到底是不
集消毒杀菌、烘干收纳为一
厨房是家里细菌较多的地方,潮湿的环境、没有完全密
实验室设备之全钢实验台如
全钢实验台是实验室家具中较为重要的家具之一,很多

推荐新闻


图片新闻

实验室药品柜的特性有哪些
实验室药品柜是实验室家具的重要组成部分之一,主要
小学科学实验中有哪些教学
计算机 计算器 一般 打孔器 打气筒 仪器车 显微镜
实验室各种仪器原理动图讲
1.紫外分光光谱UV分析原理:吸收紫外光能量,引起分
高中化学常见仪器及实验装
1、可加热仪器:2、计量仪器:(1)仪器A的名称:量
微生物操作主要设备和器具
今天盘点一下微生物操作主要设备和器具,别嫌我啰嗦
浅谈通风柜使用基本常识
 众所周知,通风柜功能中最主要的就是排气功能。在

专题文章

    CopyRight 2018-2019 实验室设备网 版权所有 win10的实时保护怎么永久关闭