Vue3源码阅读(五)ref

ref

  • 实现源码在packsages/reactivity/ref.ts
  • 接受一个参数值并返回一个响应式且可改变的 ref 对象。ref 对象拥有一个指向内部值的单一属性 .value。
  • ref 跟 reactive 都是响应系统的核心方法,作为整个系统的入口
  • 可以将 ref 看成 reactive 的一个变形版本
    • reactive 内部采用 Proxy 来实现,而 Proxy 只接受对象作为入参,这才有了 ref 来解决值类型的数据响应,如果传入 ref 的是一个对象,内部也会调用 reactive 方法进行深层响应转换
    const count = ref(0)
    console.log(count.value) // 0
    count.value++
    console.log(count.value) // 1
  • ref是如何创建的
    • 如果不是接着判断是不是浅观察,
    // 
    function createRef(rawValue: unknown, shallow = false) {
        // 先判断 value 是否已经是个ref, 如果是则直接返回
        if (isRef(rawValue)) {
            return rawValue
        }
        // 如果不是ref再构造一个ref返回
        return new RefImpl(rawValue, shallow)
    } 

    // 如果是浅观察直接构造一个 ref 返回,不是则将 rawValue 转换成 reactive再构造一个ref返回
    class RefImpl<T> {
    private _value: T
    private _rawValue: T

    public dep?: Dep = undefined
    public readonly __v_isRef = true

    constructor(value: T, public readonly _shallow = false) {
        this._rawValue = _shallow ? value : toRaw(value)
        this._value = _shallow ? value : convert(value)
    }

    get value() {
        trackRefValue(this)
        return this._value
    }

    set value(newVal) {
        newVal = this._shallow ? newVal : toRaw(newVal)
        if (hasChanged(newVal, this._rawValue)) {
        this._rawValue = newVal
        this._value = this._shallow ? newVal : convert(newVal)
        triggerRefValue(this, newVal)
        }
    }
    }

    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)
            }
        }
    }

    export function trackRefValue(ref: RefBase<any>) {
        if (isTracking()) {
            ref = toRaw(ref)
            if (!ref.dep) {
            ref.dep = createDep()
            }
            if (__DEV__) {
            trackEffects(ref.dep, {
                target: ref,
                type: TrackOpTypes.GET,
                key: 'value'
            })
            } else {
            trackEffects(ref.dep)
            }
        }
    }
  • 总结
    • createRef函数内部会先对value进行判断,如果已经是ref对象的话,直接返回当前value,否则就调用new RefImpl来生成ref对象进行返回。
    • constructor里面会判断是否是浅观察_shallow,浅的话,直接返回_rawValue,否则调用convert来返回;可以看到除了私有属性_value外,还有一个__v_isRef的只读属性为true;
    • convert里面则会对val进行判断了,对象则调用reactive,否则直接返回val,此处也就可以看到上面ref也可以接受对象作为参数的缘由了。
    • get里面会跟踪调用轨迹,track;返回当前value;
    • set里面会调用hasChanged判断是否发生了改变,此处会对NaN进行check,因为NaN与啥都不相等;设置新的值,同时调用trigger触发set调用。

ref.spec单元测试

// 带有value的对象可响应
it('should hold a value', () => {
  const a = ref(1)
  expect(a.value).toBe(1)
  a.value = 2
  expect(a.value).toBe(2)
})

it('should be reactive', () => {
  const a = ref(1)
  let dummy
  let calls = 0
  effect(() => {
    calls++
    dummy = a.value
  })
  expect(calls).toBe(1)
  expect(dummy).toBe(1)
  a.value = 2
  expect(calls).toBe(2)
  expect(dummy).toBe(2)
  // same value should not trigger
  a.value = 2
  expect(calls).toBe(2)
  expect(dummy).toBe(2)
})
// 嵌套的属性可响应
it('should make nested properties reactive', () => {
  const a = ref({
    count: 1
  })
  let dummy
  effect(() => {
    dummy = a.value.count
  })
  expect(dummy).toBe(1)
  a.value.count = 2
  expect(dummy).toBe(2)
})
// 传递空值可响应
it('should work without initial value', () => {
  const a = ref()
  let dummy
  effect(() => {
    dummy = a.value
  })
  expect(dummy).toBe(undefined)
  a.value = 2
  expect(dummy).toBe(2)
})
// ref 在 reactive 中会嵌套时解开被转换成原始值而非ref
it('should work like a normal property when nested in a reactive object', () => {
    const a = ref(1)
    const obj = reactive({
        a,
        b: {
        c: a
        }
    })
    let dummy1: number
    let dummy2: number

    effect(() => {
        dummy1 = obj.a
        dummy2 = obj.b.c
    })
    const assertDummiesEqualTo = (val: number) =>
        [dummy1, dummy2].forEach(dummy => expect(dummy).toBe(val))
    assertDummiesEqualTo(1)
    a.value++
    assertDummiesEqualTo(2)
    obj.a++
    assertDummiesEqualTo(3)
    obj.b.c++
    assertDummiesEqualTo(4)
    })
//嵌套时自动解开
it('should unwrap nested ref in types', () => {
  const a = ref(0)
  const b = ref(a)

  expect(typeof (b.value + 1)).toBe('number')
})

it('should unwrap nested values in types', () => {
  const a = {
    b: ref(0)
  }

  const c = ref(a)

  expect(typeof (c.value.b + 1)).toBe('number')
})

it('should NOT unwrap ref types nested inside arrays', () => {
  const arr = ref([1, ref(1)]).value
  ;(arr[0] as number)++
  ;(arr[1] as Ref<number>).value++

  const arr2 = ref([1, new Map<string, any>(), ref('1')]).value
  const value = arr2[0]
  if (isRef(value)) {
    value + 'foo'
  } else if (typeof value === 'number') {
    value + 1
  } else {
    // should narrow down to Map type
    // and not contain any Ref type
    value.has('foo')
  }
}) 
// 会检测传递 ref 的值类型,如果是引用类型就reactive,不是直接返回结果
it('should keep tuple types', () => {
  const tuple: [number, string, { a: number }, () => number, Ref<number>] = [
    0,
    '1',
    { a: 1 },
    () => 0,
    ref(0)
  ]
  const tupleRef = ref(tuple)

  tupleRef.value[0]++
  expect(tupleRef.value[0]).toBe(1)
  tupleRef.value[1] += '1'
  expect(tupleRef.value[1]).toBe('11')
  tupleRef.value[2].a++
  expect(tupleRef.value[2].a).toBe(2)
  expect(tupleRef.value[3]()).toBe(0)
  tupleRef.value[4].value++
  expect(tupleRef.value[4].value).toBe(1)
})
it('should keep symbols', () => {
  const customSymbol = Symbol()
  const obj = {
    [Symbol.asyncIterator]: { a: 1 },
    [Symbol.unscopables]: { b: '1' },
    [customSymbol]: { c: [1, 2, 3] }
  }
  const objRef = ref(obj)
  expect(objRef.value[Symbol.asyncIterator]).toBe(obj[Symbol.asyncIterator])
  expect(objRef.value[Symbol.unscopables]).toBe(obj[Symbol.unscopables])
  expect(objRef.value[customSymbol]).toStrictEqual(obj[customSymbol])
})
// unref 可以将 ref 还原成原始值
test('unref', () => {
  expect(unref(1)).toBe(1)
  expect(unref(ref(1))).toBe(1)
})
// shallowRef 不会发生响应,替换掉整个对象会触发响应
test('shallowRef', () => {
  const sref = shallowRef({ a: 1 })
  expect(isReactive(sref.value)).toBe(false)

  let dummy
  effect(() => {
    dummy = sref.value.a
  })
  expect(dummy).toBe(1)

  sref.value = { a: 2 }
  expect(isReactive(sref.value)).toBe(false)
  expect(dummy).toBe(2)
})
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值