• 解析Vue3源码(二)——ref


    前言

    官方对ref的说明是
    在这里插入图片描述

    vue2的ref是用来操作某个元素节点的,可以说是document.getElementById的语法糖。vue3的ref延续了vue2的用法,还增加了一个作用就是创建响应式数据。相比reactive,ref可以监听基本类型数据。尤大大也是建议创建响应式数据更多使用ref来代替reactive。

    ref使用

    vue2

    1. 在组件上声明
    2. 然后通过this.$ref使用
      this.$refs.form.validate()
      this.$refs.form获取到对应element实例,就可以使用实例上的属性

    vue3

    第一种使用方式:操作元素节点

    1. 在组件上声明
    2. 需要手动声明再使用,Vue3基本取消this的使用,也就是更多的内容为声明式使用
      let form = ref();
    3. 然后通过form.value操作其内容
      form.value.validate()

    需要注意的是在vue2中,通过ref是可以获取子组件的所有内容的,而vue3中如果是在组件上声明ref,是无法用ref获取子组件内部的元素属性的,子组件需要通过defineExpose手动暴露给外部组件

    第二种使用方式:声明响应式数据

    如下例子:

    1. 数字自增自减
    <div>{{ count }}</div>
    <button @click="add"></button>
    <button @click="subtract">-</button>
    
    let count = ref(0);
    const add =()=>{count.value++;}
    const subtract = ()=>{count.value--;}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    1. 给对象添加自定义属性
    <div>{{obj}}</div>
    <input v-model="key">
    <button @click="add">+</button>
    
    let obj = ref({});
    let key = ref('');
    const add =()=>{
        obj.value[key.value] = key.value;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    结果如下
    在这里插入图片描述
    当然这里的obj也可以用reactive响应式,但是key就不能使用了,原因参考之前讲的reactive原理。

    下面看看ref的实现原理

    ref源码

    function ref(value) {
        return createRef(value, false);
    }
    function isRef(r) {
       return !!(r && r.__v_isRef === true);
    }
    //判断当前数据是否已经是ref数据,是则直接返回。否则返回一个新new的RefImpl实例
    function createRef(rawValue, shallow) {
        if (isRef(rawValue)) {
            return rawValue;
        }
        return new RefImpl(rawValue, shallow);
    }
    
    class RefImpl {
        constructor(value, __v_isShallow) {
            this.__v_isShallow = __v_isShallow;//标志位来表明是否是浅响应数据(shallowRef时该值为true)
            this.dep = undefined;
            this.__v_isRef = true;//标志位来表明是ref类型数据。
            this._rawValue = __v_isShallow ? value : toRaw(value);//toRaw返回变量的__v_raw属性或者变量本身
            this._value = __v_isShallow ? value : toReactive(value);//如果是浅响应对象(shallowRef)则返回对象本身,否则进行监听,返回代理对象。
        }
        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); //通知各依赖更新数据
            }
        }
    }
    //可以看到 返回的跟reactive的数据是一样的。
    const toReactive = (value) => isObject(value) ? reactive(value) : value;
    
    function trackRefValue(ref) {
        if (shouldTrack && activeEffect) {
            ref = toRaw(ref);
            if ((process.env.NODE_ENV !== 'production')) {
                trackEffects(ref.dep || (ref.dep = createDep()), {
                    target: ref,
                    type: "get" /* GET */,
                    key: 'value'
                });
            }
            else {
                trackEffects(ref.dep || (ref.dep = createDep()));
            }
        }
    }
    
    function triggerRefValue(ref, newVal) {
        ref = toRaw(ref);
        if (ref.dep) {
            if ((process.env.NODE_ENV !== 'production')) {
                triggerEffects(ref.dep, {
                    target: ref,
                    type: "set" /* SET */,
                    key: 'value',
                    newValue: newVal
                });
            }
            else {
                triggerEffects(ref.dep);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70

    可以看到实例内部的value属性的get,set方法,这也就是为什么使用ref数据时要用.value来获取其数据。
    在返回的实例的构造函数内对当前数据进行了reactive返回了代理对象给_value,在get value时直接返回_value
    再看他的get方法,是先进行了依赖收集,再返回实例的_value属性
    set方法中判断数据是否改变,如果改变对数据再次reactive响应,并调用triggerEffects,更新相关依赖。
    那么是如何区分ref创建的是元素节点还是纯响应式数据呢。
    在patch时,会从标签的props中拿到ref属性。然后把当前节点挂载到ref的value属性

    const patch = (n1, n2, container, ...){
    	//...
    	const { type, ref, shapeFlag } = n2;
    	//...
    	// set ref
        if (ref != null && parentComponent) {
            setRef(ref, n1 && n1.ref, parentSuspense, n2 || n1, !n2);
        }
    }
    function setRef(rawRef, oldRawRef, parentSuspense, vnode, isUnmount = false) {
    	//...
    	 const refValue = vnode.shapeFlag & 4 /* STATEFUL_COMPONENT */
    	    ? getExposeProxy(vnode.component) || vnode.component.proxy
    	     : vnode.el;
    	 const value = isUnmount ? null : refValue;
    	// ...
    	ref.value = value;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    总结

    在声明基础类型的响应式数据或者操作元素节点时ref都是一个很好的使用方式,需要注意的是在对子组件进行操作时,如果获取不到子组件的某些属性,就要查看子组件内部是否有将该属性通过defineExpose暴露出去。

  • 相关阅读:
    python调用c++版本dll03-简单的函数调用
    四叉堆在GO中的应用-定时任务timer
    使用 Alice inspector 和 Dio 进行 Flutter API 日志记录
    STM32-程序占用内存大小计算
    【Vue】自定义事件实现组件之间的通信(案例讲解)
    深紫色粉末BHQ-1 NHS,916753-61-2,NHS修饰是合成后与一个伯氨基的共轭
    Leetcode81. Search in Rotated Sorted Array II
    springboot配置定时任务功能
    计算机字符编码方式
    Kafka 就这么点东西,不难,直接拿走
  • 原文地址:https://blog.csdn.net/qq_41028148/article/details/127651661