Vue3-ref

611 阅读3分钟

使用ref()的一个实例

<template>
    <div>{{ count }}</div>
    <div><button @click="add">点击加1</button></div>
</template>

<script>

import { ref } from 'vue'
export default {
    setup(){
        const count = ref(0)
        
        function add(){
            count.value ++ 
        }

        return {
            count,
            add
        }
    }
}
</script>

ref()源码实现

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

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

// val=0,如果val是对象(包含数组/Map/Set/WeakMap/WeakSet),则调用reactive()变成响应式;如果不是对象,则直接返回val
// reactive就是将我们的数据转成响应式数据
const convert = (val) => isObject(val) ? reactive(val) : val;


class RefImpl {
    constructor(_rawValue, _shallow = false) {
        this._rawValue = _rawValue;
        this._shallow = _shallow;
        this.__v_isRef = true;
        this._value = _shallow ? _rawValue : convert(_rawValue);
    }
    // 在类的内部使用get和set关键字,对属性value设置存储函数和取值函数,拦截该属性的取存行为。
    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);
        }
    }
}

# ref()返回的最终结果

image.png

ref()深入理解

这里最难理解的就在于这个ref函数。 我们看到,这里也定义了get/set,却没有任何Proxy相关的操作。 在之前的信息中我们知道reactive能构建出响应式数据,但要求传参必须是对象。 ref的入参是对象时,同样也需要reactive做转化。 那ref这个函数的目的到底是什么呢?为什么需要它? 对于基本数据类型、函数传递或者对象结构时,会丢失原始数据的引用, 换言之,我们没法让基本数据类型,或者结构的变量(如果他的值也是基本数据类型的话),成为响应式的数据。 但是有时候,我们确实需要将一个数字、一个字符串置为响应式, 或者就是想利用解构的写法。 那怎么办呢? 只能通过创建一个对象,也就是源码中的Ref数据,然后将原始数据保存在Ref的属性value中国 再将它的引用返回给使用者。 既然是我们自己创造出来的对象,也就没必要使用Proxy再做代理了,直接劫持这个value的get/set即可,这就是ref函数与Ref类型的由来。

ES6类Class的get、set使用

class Person {
    //构造函数
    constructor(value){
        this.name = "ysg";
        this._age = value;
    }

    get value(){
        return this._age
    }

    set value(value){
        this._age = value
    }
}

let p = new Person(18)
console.log(p.value)
console.log(p._age)
p.value = 31
console.log(p.value)
console.log(p._age)

get触发track函数进行依赖收集

  • track 跟踪
// target:触发track的对象
// type: 表示触发track的类型(主要有get、has、iterate三种类型)
// key: 表示触发当前track方法对应得object属性的key

function track(target, type, key) {
    if (!shouldTrack || activeEffect === undefined) {
        return;
    }
    let depsMap = targetMap.get(target);
    if (!depsMap) {
        targetMap.set(target, (depsMap = new Map()));
    }
    let dep = depsMap.get(key);
    if (!dep) {
        depsMap.set(key, (dep = new Set()));
    }
    if (!dep.has(activeEffect)) {
        dep.add(activeEffect);
        activeEffect.deps.push(dep);
        if ((process.env.NODE_ENV !== 'production') && activeEffect.options.onTrack) {
            activeEffect.options.onTrack({
                effect: activeEffect,
                target,
                type,
                key
            });
        }
    }
}

set触发trigger方法执行我们收集到的依赖

function trigger(target, type, key, newValue, oldValue, oldTarget) {
    const depsMap = targetMap.get(target);
    if (!depsMap) {
        // never been tracked
        return;
    }
    const effects = new Set();
    const add = (effectsToAdd) => {
        if (effectsToAdd) {
            effectsToAdd.forEach(effect => {
                if (effect !== activeEffect || effect.allowRecurse) {
                    effects.add(effect);
                }
            });
        }
    };
    if (type === "clear" /* CLEAR */) {
        // collection being cleared
        // trigger all effects for target
        depsMap.forEach(add);
    }
    else if (key === 'length' && isArray(target)) {
        depsMap.forEach((dep, key) => {
            if (key === 'length' || key >= newValue) {
                add(dep);
            }
        });
    }
    else {
        // schedule runs for SET | ADD | DELETE
        if (key !== void 0) {
            add(depsMap.get(key));
        }
        // also run for iteration key on ADD | DELETE | Map.SET
        switch (type) {
            case "add" /* ADD */:
                if (!isArray(target)) {
                    add(depsMap.get(ITERATE_KEY));
                    if (isMap(target)) {
                        add(depsMap.get(MAP_KEY_ITERATE_KEY));
                    }
                }
                else if (isIntegerKey(key)) {
                    // new index added to array -> length changes
                    add(depsMap.get('length'));
                }
                break;
            case "delete" /* DELETE */:
                if (!isArray(target)) {
                    add(depsMap.get(ITERATE_KEY));
                    if (isMap(target)) {
                        add(depsMap.get(MAP_KEY_ITERATE_KEY));
                    }
                }
                break;
            case "set" /* SET */:
                if (isMap(target)) {
                    add(depsMap.get(ITERATE_KEY));
                }
                break;
        }
    }
    const run = (effect) => {
        if ((process.env.NODE_ENV !== 'production') && effect.options.onTrigger) {
            effect.options.onTrigger({
                effect,
                target,
                key,
                type,
                newValue,
                oldValue,
                oldTarget
            });
        }
        if (effect.options.scheduler) {
            effect.options.scheduler(effect);
        }
        else {
            effect();
        }
    };
    effects.forEach(run);
}

当前template模板中有使用到count,则会给count添加render effect; 则当count.value修改,会触发页面更新。

image.png

image.png