阅读源码原则
- 简化和调试,掌握核心流程
- 不被过多细节束缚,只专注于主线
探究目的
- ref 如何进行实现的
- ref 简单数据类型的响应性是如何实现的
- ref 类型的数据,必须通过 .value 访问,为何使用这种API
Debug Ref 源码
-
先编写一个测试程序,用于debug
<script src='../../dist/vue.global.js'></script> <body> <div id='app'> </div> </body> <script> const { ref, effect } = Vue // console.log(reactive) const obj = ref({ name: '张三' }) // console.log(obj.name) // 触发getter // obj.name = '李四' // 触发 setter effect(() => { document.querySelector('#app').innerText = obj.value.name // 这里一定要暴露出来一个getter的行为,这样我们才可完成依赖的收集 }) setTimeout(() => { obj.value.name = '李四' }, 2000) </script> -
在源码 ref 函数中打上一个断点,我们走一下
export function ref(value?: unknown) { return createRef(value, false) } -
这里,走到了 createRef 函数中,我们再进入看下
function createRef(rawValue: unknown, shallow: boolean) { // 这个跳过 if (isRef(rawValue)) { return rawValue } // 会执行这个 return new RefImpl(rawValue, shallow) } -
跳入 isRef 函数
export function isRef(r: any): r is Ref { return !!(r && r.__v_isRef === true) } -
这个判断也是很简单,__v_isRef 这个标识为 true, 则是一个ref对象,目前不会走这儿
-
我们跳出,我们再看下 RefImpl 这个类
class RefImpl<T> { private _value: T private _rawValue: T public dep?: Dep = undefined // 这里是 Set public readonly __v_isRef = true // 注意看这里的标识,在这里创建实例后,就有了这个 buff 属性 // 构造函数中的 value 就是我们 name为张三的 object, __v_isShallow 这个参数是否是浅层的,可以理解为简单类型 constructor(value: T, public readonly __v_isShallow: boolean) { // 这里会走 toRaw this._rawValue = __v_isShallow ? value : toRaw(value) // 这里 _rawValue 本质上就是 value // 这里会走 toReactive this._value = __v_isShallow ? value : toReactive(value) // 这里 _value 本质上 被 reactive方法包裹变成了一个响应式对象 } 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, newVal) } } } -
进入 toRaw 函数
export function toRaw<T>(observed: T): T { const raw = observed && (observed as Target)[ReactiveFlags.RAW] return raw ? toRaw(raw) : observed // 这里return的是 observed 就是原对象 } -
跳出 toRaw 进入 toReactive
export const toReactive = <T extends unknown>(value: T): T => // 注意这里走 reactive,所以这里变成了一个响应性对象 isObject(value) ? reactive(value) : value -
看下 get 和 set value的两个函数
- 我们都知道这个语法的使用,不做过多解释
-
在 get value 函数中会触发 trackRefValue,我们看看这里, 用于完成依赖收集工作
export function trackRefValue(ref: RefBase<any>) { // 这里会走 if (shouldTrack && activeEffect) { ref = toRaw(ref) if (__DEV__) { // 注意这里,ref.dep 是一个Set集合 trackEffects(ref.dep || (ref.dep = createDep()), { target: ref, type: TrackOpTypes.GET, key: 'value' }) } else { trackEffects(ref.dep || (ref.dep = createDep())) } } } -
我们进入 trackEffects 来看下
export function trackEffects( dep: Dep, debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { let shouldTrack = false if (effectTrackDepth <= maxMarkerBits) { if (!newTracked(dep)) { dep.n |= trackOpBit // set newly tracked shouldTrack = !wasTracked(dep) } } else { // Full cleanup mode. shouldTrack = !dep.has(activeEffect!) } if (shouldTrack) { // 这里会执行,进行一对多的收集 dep.add(activeEffect!) activeEffect!.deps.push(dep) if (__DEV__ && activeEffect!.onTrack) { activeEffect!.onTrack({ effect: activeEffect!, ...debuggerEventExtraInfo! }) } } } -
这里完成了依赖的收集
-
对于 ref 而言,基于 get value 主动触发getter行为,基于 trackRefValue 来完成依赖收集工作,最终返回 this._value
-
再回到示例中的定时器中
setTimeout(() => { obj.value.name = '李四' }, 2000) -
这里,obj.value 本质上还会触发 getter 行为,但是不会执行,因为 在 trackRefValue 中 的 activeEffect 中不再有值, 因为在 run() 函数里处理了
activeEffect = this.parent shouldTrack = lastShouldTrack this.parent = undefined- 在 get value 下的 trackRefValue(this) 会跳过,不会再走依赖收集的过程
- obj.value.name 示例程序这里会触发 setter 行为,所以本质上这行代码走了两个行为
- 1 )触发了 ref 的 get value
- 2 )触发了 reactive 的 setter 行为,因为本质上触发了 proxy的setter
-
在这儿,我们可以看出
- 对于 ref 函数会返回 RefImpl 类型的实例
- 在该实例中,会根据传入的数据类型进行分开处理
- 简单类型不做处理
- 复杂数据类型,转化为 reactive 返回的 proxy 实例
- 无论我们执行 obj.value.name 还是 obj.value.name = xxx 本质上都是触发了 get value
- 之所以会进行响应性 因为 obj.value 是一个 reactive 函数生成的proxy
Ref 实现简单类型的响应式
-
新建示例程序 vue/examples/reactivity/ref-shallow.html
<script src='../../dist/vue.global.js'></script> <body> <div id='app'></div> </body> <script> const { ref, effect } = Vue const obj = ref('张三') effect(() => { document.querySelector('#app').innerText = obj.value }) const timer = setTimeout(() => { clearTimeout(timer) obj.value = '李四' }, 2000) </script> -
上述是简单的字符串类型
-
整体流程和复杂数据类型一致,我们主要关注的是 set 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) // 这里走到了 toReactive 里 triggerRefValue(this, newVal) } } -
简单数据类型和复杂数据类型不同的地方,在于 hasChanged 的if判断
// compare whether a value has changed, accounting for NaN. export const hasChanged = (value: any, oldValue: any): boolean => !Object.is(value, oldValue) -
判断两个值,是否相等,如果变化了,执行 if 结构里面的程序
-
之后,走到了 triggerRefValue里面,这个函数和之前类似
export function triggerRefValue(ref: RefBase<any>, newVal?: any) {
ref = toRaw(ref)
if (ref.dep) {
if (__DEV__) {
triggerEffects(ref.dep, {
target: ref,
type: TriggerOpTypes.SET,
key: 'value',
newValue: newVal
})
} else {
triggerEffects(ref.dep)
}
}
}
-
这个和之前是一致的
-
到现在,我们知道,ref中简单的数据类型并不是通过 proxy 来响应的,而是基于 RefImpl 的 get 和 set value 来收集和触发的,这就是 .value 的关键
- 也就是说 .value 是一个适配器,里面封装了对简单和复杂数据类型的处理
删减后的核心实现代码
- 参考上篇文章的rollup构建环境搭建
1 )复杂类型的程序处理
-
创建 reactivity/ref.ts 模块
import { toReactive } from './reactive' import { createDep, Dep } from './dep' import { activeEffect, trackEffects } from './effect' export interface Ref<T = any> { value: T } export function ref(value?: unknown) { return createRef(value, false) } function createRef(rawValue: unknown, shallow: boolean) { if (isRef(rawValue)) { return rawValue } return new RefImpl(rawValue, shallow) } class RefImpl<T> { private _value: T public dep?: Dep = undefined public readonly __v_isRef = true constructor (value: T, public readonly __v_isShallow: boolean) { this._value = __v_isShallow ? value: toReactive(value) } get value() { trackRefValue(this) return this._value } set value(newValue) { } } export function trackRefValue(ref) { if (activeEffect) { trackEffects(ref.dep || (ref.dep = createDep())) } } export function isRef(r: any): r is Ref { return !!(r && r.__v_isRef === true) } -
在 reactivity/reactive.ts 模块中创建 toReactive 函数
import { isObject } from '@vue/shared' export const toReactive = <T extends unknown>(value: T): T => isObject(value) ? reactive(value as object) : value -
在 reactivity/index.ts 中 导出 ref
export { ref } from './ref' -
创建一个复杂类型的测试实例
<script src="../dist/vue.js"></script> <body> <div id='app'> </div> </body> <script> const { ref, effect } = Vue // console.log(reactive) const obj = ref({ name: '张三' }) // console.log(obj.name) // 触发getter // obj.name = '李四' // 触发 setter effect(()=>{ document.querySelector('#app').innerText = obj.value.name // 这里一定要暴露出来一个getter的行为,这样我们才可完成依赖的收集 }) setTimeout(() => { obj.value.name = '李四' }, 2000) </script> -
基于以上,可知已完成了响应式的功能,备注,需要依赖上篇博客中的一些实现
-
以上, 我们实现了 ref 复杂数据类型响应性
- 本质还是利用 reactive 函数 来实现,都是返回一个 proxy,利用其 getter和setter来完成依赖收集和依赖触发
- 需要实现简单类型的响应式,主要区别在于 set value() 函数
2 ) 简单类型的响应性逻辑
-
在 ref.ts中添加
import { hasChanged } from '@vue/shared' class RefImpl<T> { private _rawValue: T // 添加 constructor(value: T, ) { this._rawValue = value // 添加这行 } set value(newVal) { if (hasChanged(newVal, this._rawValue)) { this._rawValue = newVal this._value = toReactive(newVal) triggerRefValue(this) } } } export function triggerRefValue(ref) { if (ref.dep) { triggerEffects(ref.dep) } } -
新建简单类型测试程序
<script src="../dist/vue.js"></script> <body> <div id='app'> </div> </body> <script> const { ref, effect } = Vue const obj = ref('张三') effect(()=>{ document.querySelector('#app').innerText = obj.value // 这里一定要暴露出来一个getter的行为,这样我们才可完成依赖的收集 }) setTimeout(() => { obj.value = '李四' }, 2000) </script> -
此时,就完成了 自己实现的 ref 响应式了
- ref 简单类型响应性不具备数据监听的概念, 而是通过 get和set value来实现的对简单类型的处理
- ref 是一个适配器, 对复杂和简单类型分别处理
总结
- ref做的三件事情
- 返回RefImpl实例
- 在构造函数中,对传入数据进行处理:当前传入value是简单类型,则直接使用;否则,使用reactive函数包裹,让ref.value的值变成响应式数据
- 启动两个函数:get value, set value
312



被折叠的 条评论
为什么被折叠?



