记一次给Vue3提pr的感悟

3,368 阅读4分钟

如标题所见,我给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被尤大亲手关了,不过他也给出了解释:

image.png

他的意思是unref只是想拿到refvalue,并不想去除它的响应性,是有意识的这样设计的。

不错,虽然心有遗憾,但我也只能thanks, I got it.,不过也真的学到了不少,不得不说vue3的设计的确很精妙,继续啃吧,我的路还很长,希望早日攻克vue3。

最后

到目前为止,我接触vue3只有短短半年左右的时间,开始看源码、实现简单逻辑也没有几个月时间,但是真心觉得大有所获,真的还是要归功于崔大mini-vue,目前已经有4.1K的star了,增进飞快,崔大也出了配套的课程(收费),价格方面和市面上的源码课程比起来真的没得说。

他的课程很硬核、很干货,对新手不那么友好,以至于一直没有火起来,但是内容真是实打实的不错,还有崔大配套的答疑服务,对课程感兴趣的朋友可以找我了解下哈。