Vue3响应式API-ref

743 阅读3分钟

前言

版本:3.0.2

说明:通过源码对Vue3的响应式API-ref进行阐述

一、响应式API

ref

作用:

  • 实现值类型的数据响应式
  • 绑定模板的DOM元素

使用方式:

html

<div id="app">
  <div>{{ count }}</div>
  <div ref="eleRef">ref</div>
  <button @click="add">添加</button>
</div>

JavaScript

const { ref, createApp, onMounted } = Vue;
const app = createApp({
  setup() {
    const count = ref(1);
    
    // 1、在这里修改count的值,必须通过.value设置
    count.value++;
    
    const eleRef = ref(null);
    
    // 页面挂载完毕后执行
    onMounted(() => {
      console.log(eleRef.value); // => <div>ref</div>
    });
    
    // 2、将页面中添加了ref属性的元素与setup返回的ref对象绑定
    return {
      count,
      eleRef
    }
  },
  methods: {
    add() {
      // 3、这里不能通过.value进行赋值
      this.count += 1;
    }
  }
}).mount("#app");

二、源码解析

通过对源码的分析,剖析上面JS代码中的三点重要注释。

  • 1、在这里修改count的值,必须通过.value设置

创建ref对象。

function ref(value) {
  return createRef(value);
}

function createRef(rawValue, shallow = false) {
  if (isRef(rawValue)) {
    return rawValue;
  }
  return new RefImpl(rawValue, shallow);
}

其中isRef为检查值是否为一个 ref 对象。

function isRef(r) {
  return Boolean(r && r.__v_isRef === true);
}

RefImpl为ref对象的实现类。通过源码我们可以得知,在该类中定义了get value()set value(newVal)分别用来取值和赋值,这就解释了第一点注释。

class RefImpl {
  constructor(_rawValue, _shallow = false) {
    this._rawValue = _rawValue;
    this._shallow = _shallow;
    this.__v_isRef = true;
    // 如果为浅层的,返回原始数据,否则将_value转成响应式对象
    this._value = _shallow ? _rawValue : convert(_rawValue);
  }
  get value() {
    track(toRaw(this), "get" /* GET */, 'value');
    return this._value;
  }
  set value(newVal) {
    if (hasChanged(toRaw(newVal), this._rawValue)) {
      this._rawValue = newVal;
      this._value = this._shallow ? newVal : convert(newVal);
      trigger(toRaw(this), "set" /* SET */, 'value', newVal);
    }
  }
}

convert方法将对象类型的参数转成响应式对象。

关于tracktrigger方法,涉及到响应式原理部分,推荐阅读我的前一篇文章。

传送门:Vue3响应式原理详解

  • 2、将页面中添加了ref属性的元素与setup返回的ref对象绑定

处理setup函数的返回结果。

function handleSetupResult(instance, setupResult, isSSR) {
  // 省略部分源码...
  
  instance.setupState = proxyRefs(setupResult);
}

为ref数据添加拦截。

function unref(ref) {
  // 如果为ref类型,则返回ref的value
  return isRef(ref) ? ref.value : ref;
}
const shallowUnwrapHandlers = {
  // 如果获取的属性为ref类型,则触发响应,更新界面
  get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)),
  set: (target, key, value, receiver) => {
    const oldValue = target[key];
    if (isRef(oldValue) && !isRef(value)) {
      oldValue.value = value;
      return true;
    }
    else {
      return Reflect.set(target, key, value, receiver);
    }
  }
};
function proxyRefs(objectWithRefs) {
  return isReactive(objectWithRefs)
    ? objectWithRefs
  : new Proxy(objectWithRefs, shallowUnwrapHandlers);
}

setupState添加ref对象。下方代码中的setupState[ref] = value;部分,会将eleRef的value设置为页面中添加了ref="eleRef"DOM

const setRef = (rawRef, oldRawRef, parentComponent, parentSuspense, vnode) => {
  // 省略部分源码...
  
  let value;
  // ...
  
  // value为ref绑定的DOM元素
  value = vnode.el;
  
  // 为setupState添加ref对象。
  const doSet = () => {
    refs[ref] = value;
    if (hasOwn(setupState, ref)) {
      // 将DOM元素和ref绑定
      // 设置值时会触发shallowUnwrapHandlers的set拦截,即:将ref对象的value属性设置为新值
      setupState[ref] = value;
    }
  };
}
  • 3、这里不能通过.value进行赋值

暴露setupState属性到Vue实例中。

所以在第三点中,直接用this.count += 1;,等同于this._.setupState.count,取值操作会触发shallowUnwrapHandlers方法的get方法,返回ref的value。

// 通过vm.[setupState]取值时,取的是instance.setupState[key]
function exposeSetupStateOnRenderContext(instance) {
  const { ctx, setupState } = instance;
  Object.keys(toRaw(setupState)).forEach(key => {
    if (key[0] === '$' || key[0] === '_') {
      // setup返回的对象属性中不能以`$`或`_`开头
      warn(`setup() return property ${JSON.stringify(key)} should not start with "$" or "_" ` +
           `which are reserved prefixes for Vue internals.`);
      return;
    }
    Object.defineProperty(ctx, key, {
      enumerable: true,
      configurable: true,
      get: () => setupState[key],
      set: NOOP
    });
  });
}

看完3件事

1、如果文章对你有帮助,可以给博主点个赞。

2、如果你觉得文章还不错,可以动动你的小手,收藏一下。

3、如果想看更多的源码详解,可以添加关注博主。

附录:

1、Vue3.x完整版源码解析:github.com/fanqiewa/vu…

2、其它源码解析:www.fanqiewa.xyz/