如标题所见,我给vue3提了一次pr,但是并没有通过哈哈哈,但是整个过程让我学到了很多,特此记录一下这次难得的经历。
pr点从哪里来
不得不提到崔大的mini-vue,也是这个库带我走进了vue3的源码世界。
通过崔大的源码课程,我在实现unref
时,我发现了一个值得思考的问题:
// 源码中的unref实现
export function unref<T>(ref: T | Ref<T>): T {
return isRef(ref) ? (ref.value as any) : ref
}
我们可以看到,源码中将传入的值进行判断,若是ref
对象择返回它的value
属性值,如果不是ref
对象,则返回传入的值。
也就是等于说,unref
会把创建ref
时传入的值返回出来。
我写到这里的时候,突然隐隐觉得哪里不太对,仔细回忆之后,想起来一个在实现ref时的细节:
// 源码中ref类的实现
class RefImpl<T> {
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) {
newVal = this.__v_isShallow ? newVal : toRaw(newVal)
if (hasChanged(newVal, this._rawValue)) {
this._rawValue = newVal
this._value = this.__v_isShallow ? newVal : toReactive(newVal)
triggerRefValue(this, newVal)
}
}
}
可以看到源码的实现是对ref(或shallowRef)
传入的值进行了判断,若调用的是shallowRef
则直接保存传入的value
,不进行响应式处理,若调用的是ref
,则对传入的值进行toReactive
的处理:
// 源码中toReactive的实现
export const toReactive = <T extends unknown>(value: T): T =>
isObject(value) ? reactive(value) : value
可以看到,也对传入的值进行了判断,若值为一个object
对象,则返回reactive(value)
,否则返回value
。
所以,当ref
中传入的是object
对象时,会把值用reactive
包装起来,则调用ref.value
时则实现了响应式。很巧妙,但是问题来了,当我想获取ref解绑后的“静态”值时,我调用unref(value)
返回的也是ref.value
,还是响应式的proxy
对象,并不是单纯的object
。
按照我的逻辑,如果用户只想拿到最纯净的object
,那这个unref
的实现显然不符合预期,抱着这份坚定,我开始了我的pr。
pr的过程
先fork了vue3,然后拉到本地,pnpm装了相关依赖,然后就开始在ref.spec.ts里加单元测试:
test('unref', () => {
const foo = {
bar: 1
}
expect(isReactive(unref(ref(foo)))).toBe(false) // should not be reactive
expect(unref(ref(foo))).toBe(foo)
})
分析需求,我们需要返回reactive
包装过的object
原始值,刚好reactive
中实现了一个toRaw
方法就是这个方法,于是我对原方法进行了改造:
export function unref<T>(ref: T | Ref<T>): T {
// return isRef(ref) ? (ref.value as any) : ref
return isRef(ref) ? (toRaw(ref.value) as any) : ref
}
很顺利,单元测试通过了,然后我对其他所有的模块也跑了下单元测试,也全都通过,说明这个改动没有影响到别的模块。
完成,push到我的仓库,然后提交pr,开始静静等待vue团队审视代码……
当然,结果已经出来了,大家不用等待哈哈哈,先放个链接github.com/vuejs/core/…
pr的结果
过程中也有很多contributor在提出疑问,有说可以通过toRaw(unref(ref))
来解决的,也有说我的改动会影响computed
计算属性收集依赖,的确是会存在些问题,当我发觉我的疑问已经不仅仅是某个功能不完善的层面上了,而是vue3在设计unref
这个函数时,最初的意图是想解决什么问题,这个函数要实现什么效果?
这不是我能解答的了,得等尤大来康康了哈哈哈,思考到这里,我感觉我的pr多半是凉了。
没过几天,不出所料,我的pr被尤大亲手关了,不过他也给出了解释:
他的意思是unref
只是想拿到ref
的value
,并不想去除它的响应性,是有意识的这样设计的。
不错,虽然心有遗憾,但我也只能thanks, I got it.
,不过也真的学到了不少,不得不说vue3的设计的确很精妙,继续啃吧,我的路还很长,希望早日攻克vue3。
最后
到目前为止,我接触vue3只有短短半年左右的时间,开始看源码、实现简单逻辑也没有几个月时间,但是真心觉得大有所获,真的还是要归功于崔大的mini-vue,目前已经有4.1K的star了,增进飞快,崔大也出了配套的课程(收费),价格方面和市面上的源码课程比起来真的没得说。
他的课程很硬核、很干货,对新手不那么友好,以至于一直没有火起来,但是内容真是实打实的不错,还有崔大配套的答疑服务,对课程感兴趣的朋友可以找我了解下哈。