设计

对于原始值(Primitive values),按照值传递,而非按引用传递。

JavaScript 中的 Proxy 无法提供对原始值的代理,因此想要将原始值变成响应式数据,必须对其做一层包裹,即 Vue.js 中 ref

源码实现

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

function trackRefValue(ref: RefBase<any>) {
  if (shouldTrack && activeEffect) {
    ref = toRaw(ref)
    if (__DEV__) {
      trackEffects(ref.dep || (ref.dep = createDep()), {
        target: ref,
        type: TrackOpTypes.GET,
        key: 'value'
      })
    } else {
      trackEffects(ref.dep || (ref.dep = createDep()))
    }
  }
}

export const toReactive = (value) => isObject(value) ? reactive(value) : value

export function toRaw(observed) {
  const raw = observed && observed['__v_raw']
  return raw ? toRaw(raw) : observed
}

ref() API 返回的是一个包装对象(value reference)?

JavaScript 中分基本数据类型和引用数据类型,基本数据类型是通过值传递(非引用传递)。如果在一个函数中返回一个字符串变量,接收到这个字符串的代码只会获得一个值,是无法追踪原始变量后续的变化的。

而对于在 template 中可以使用,没有使用 .value。因为包装对象被暴露给模版渲染上下文,或是被嵌套在另一个响应式对象中的时候,它会被自动展开 (unwrap) 为内部的值,。理解为:做了拆箱操作——包装对象的自动展开(Ref Unwrapping)


Ref 和 Reactive 差异

Ref

import { ref } from 'vue'

let foo = 0
let bar = ref(0)

foo = 1
bar = 1 // ts-error

PROS

CONS

Reactive

import { reactive } from 'vue'

const foo = { prop: 0 }
const bar = reactive({ prop: 0 })

foo.prop = 1
bar.prop = 1

PROS

CONS

Ref 自动解包操作?

const counter = ref(0)

watch(counter, count => {
  console.log(count) // same as Icounter.value'
})
<template>
  <button  @click="counter += 1">
    Counter is {{ counter }}
  </button>
</template>
import { ref, reactive } from 'vue'
const foo = ref('bar')
const data = reactive({ foo, id: 10 })
data.foo // 'bar'