Ref & ShallowRef

ref:接受一个参数值并返回一个响应式且可改变的 ref 对象。ref 对象拥有一个指向内部值的单一属性 .value

可以将 ref 看成 reactive 的一个变形版本,这是由于 reactive 内部采用 Proxy 来实现,而 Proxy 只接受对象作为入参,这才有了 ref 来解决值类型的数据响应,如果传入 ref 的是一个对象,内部也会调用 reactive 方法进行深层响应转换

const count = ref(0)console.log(count.value) // 0count.value++console.log(count.value) // 1

shallowRef:ref() 的浅层作用形式。和 ref() 不同,浅层 ref 的内部值将会原样存储和暴露,并且不会被深层递归地转为响应式。只有对 .value 的访问是响应式的。

const state = shallowRef({ count: 1 })// 不会触发更改state.value.count = 2// 会触发更改state.value = { count: 2 }

源码实现

  • @issue1 如果是对象和数组,则调用 reactive方法 转化为响应式对象(ref会转换,shallowRef不会转换)
  • @issue2 getter 取值的时候收集依赖
  • @issue3 setter 设置值的时候触发依赖
/** * @desc 如果是对象和数组,则转化为响应式对象 */function toReactive(value) {  return isObject(value) ? reactive(value) : value}/** * @desc RefImpl * @issue1 如果是对象和数组,则转化为响应式对象 */class RefImpl {  // ref标识  public __v_isRef = true  // 存储effect  public dep = new Set()  public _value  constructor(public rawValue, public _shallow) {    // @issue1    this._value = _shallow ? rawValue : toReactive(rawValue)  }  get value() {    // 取值的时候收集依赖    trackEffects(this.dep)    return this._value  }  set value(newValue) {    // 新旧值不相等    if (newValue !== this.rawValue) {      // @issue1      this._value = this._shallow ? newValue : toReactive(newValue)      this.rawValue = newValue      // 设置值的时候触发依赖      triggerEffects(this.dep)    }  }}// 接受一个内部值,返回一个响应式的、可更改的 ref 对象,此对象只有一个指向其内部值的属性 .value。export function ref(value) {  return new RefImpl(value)}

测试代码

/** * 1. Ref **/const person = ref({  name: '柏成',  age: 25,})effect(() => {  app.innerHTML = person.value.name})setTimeout(() => {  person.value.name = '柏成2号' // 会触发更改}, 1000)/** * 2. shallowRef */const person = shallowRef({  name: '柏成',  age: 25,})effect(() => {  app.innerHTML = person.value.name})setTimeout(() => {  person.value.name = '柏成2号' // 不会触发更改}, 1000)setTimeout(() => {  person.value = {    name: '柏成9号' // 会触发更改  }}, 2000)

toRef & toRefs

toRef:基于响应式对象上的一个属性,创建一个对应的 ref。这样创建的 ref 与其源属性保持同步:改变源属性的值将更新 ref 的值,反之亦然。

const state = reactive({  foo: 1,  bar: 2})const fooRef = toRef(state, 'foo')// 更改该 ref 会更新源属性fooRef.value++console.log(state.foo) // 2// 更改源属性也会更新该 refstate.foo++console.log(fooRef.value) // 3

toRefs:将一个响应式对象转换为一个普通对象,这个普通对象的每个属性都是指向源对象相应属性的 ref。每个单独的 ref 都是使用 toRef() 创建的。

const state = reactive({  foo: 1,  bar: 2})const stateAsRefs = toRefs(state)// 这个 ref 和源属性已经 “链接上了”state.foo++console.log(stateAsRefs.foo.value) // 2stateAsRefs.foo.value++console.log(state.foo) // 3

源码实现

class ObjectRefImpl {  // 只是将.value属性代理到原始类型上  constructor(public object, public key) {}    get value() {    return this.object[this.key]  }    set value(newValue) {    this.object[this.key] = newValue  }}// 基于响应式对象上的一个属性,创建一个对应的 ref。这样创建的 ref 与其源属性保持同步:改变源属性的值将更新 ref 的值,反之亦然。export function toRef(object, key) {  return new ObjectRefImpl(object, key)}// 将一个响应式对象转换为一个普通对象,这个普通对象的每个属性都是指向源对象相应属性的 ref。每个单独的 ref 都是使用 toRef() 创建的。export function toRefs(object) {  const result = isArray(object) ? new Array(object.length) : {}  for (let key in object) {    result[key] = toRef(object, key)  }  return result}

测试代码

// 对象const person = reactive({  name: '柏成',  age: 18})// 数组const numbers = reactive([1, 2, 3, 4, 5])// 注意!直接解构后会丢失响应性的特点!!!// let { name, age } = personconst {  name,  age} = toRefs(person)const [first] = toRefs(numbers)effect(() => {  app.innerHTML = `${name.value},${age.value}岁。第一个数字为${first.value}。`})setTimeout(() => {  name.value = '柏成9号'  first.value = 999}, 1000)

自动脱ref

在js中访问ref时需要.value获取,但是在模版中却可以直接取值,不需要加.value!这里就用到了 proxyRefs 自动脱ref方法

源码实现

export function proxyRefs(object) {  return new Proxy(object, {    // 代理的思想,如果是ref 则取ref.value    get(target, key, recevier) {      let r = Reflect.get(target, key, recevier)      return r.__v_isRef ? r.value : r    },    // 设置的时候如果是ref,则给ref.value赋值    set(target, key, value, recevier) {      let oldValue = target[key]      if (oldValue.__v_isRef) {        oldValue.value = value        return true      } else {        return Reflect.set(target, key, value, recevier)      }    },  })}

测试代码

const name = ref('柏成')const age = ref('24')const person = proxyRefs({  name,  age,  sex: '男'})effect(() => {  app.innerHTML = `${person.name},${person.age}岁。性别${person.sex}。`})setTimeout(() => {  name.value = '柏成9号'}, 1000)

人间不正经生活手册