[Vue源码系列-4]vue3的Ref实现原理

164 阅读3分钟

1. reactiveref的区别

  • reactiveref都是vue3中提供的响应式API,用于定义响应式数据的
  • reactive通常用于定义对象数据类型,其本质是基于 Proxy 实现对象代理,所以reactive不能用于定义基本类型数据
  • ref通常是用于定义基本数据类型,其本质是基于 Object.defineProperty() 重新定义属性的方式实现,vue3源码中是基于类的属性访问器实现(本质也是 defineProperty )

2. Ref

2.1 实现RefshallowRef

ref的本质是基于类的属性访问器实现的,可以将一个基本类型值进行包装

  1. craeteRef普通类型变成一个对象类型
    • ref值也可以是对象,但是一般情况下是对象直接使用reactive更合理
  2. RefImpl 类
    • beta版本之前版本ref是个对象,由于对象不方便拓展,所以改成了类
    • 如果传递 shallow = true 则只是代理最外层
  3. get value 属性访问器
    • track收集依赖
    • 代理 value 会帮我们代理到 _value
  4. set value 属性设置器
    • 判断新值老值是否改变,改变就更新值
    • trigger 触发依赖执行
// packages/reactivity/ref.ts
import { hasChanged, isObject } from "@vue/shared";
import { track, trigger } from "./effect";
import { TrackOpTypes, TriggerOpTypes } from "./operations";
import { reactive } from "./reactive";

export function ref(value) { // value 是一个基本数据类型
    // 将 普通类型 变成一个 对象类型 
    return createRef(value);
}

export function shallowRef(value) { // shallowRef Api
    return createRef(value, true);
}
function createRef(rawValue, shallow = false) {
    return new RefImpl(rawValue, shallow)
}

const convert = (val) => isObject(val) ? reactive(val) : val; // 递归响应式

class RefImpl {
    private _value;
    public readonly __v_isRef = true; // 产生的实例会被添加 __v_isRef 表示是一个 ref 属性
    constructor(private _rawValue, public readonly _shallow) {
        // 如果是深度,需要把里面的都变成响应式的
        this._value = _shallow ? _rawValue : convert(_rawValue)
    }
	// 类的属性访问器
    get value() {    // 代理 去value 会帮我们代理到 _value 上
        track(this, TrackOpTypes.GET, 'value');
        return this._value;
    }
    set value(newVal) {
        if (hasChanged(newVal, this._rawValue)) { // 判断新老值是否有变化
            this._rawValue = newVal; // 保存值
            this._value = this._shallow ? newVal : convert(newVal);
            trigger(this, TriggerOpTypes.SET, 'value', newVal);
        }
    }
}

2.2 实现toRefs

将对象(数组)中的属性转换成ref属性,实现批量转化

class ObjectRefImpl{
    public readonly __v_isRef = true
    constructor(private readonly _object, private readonly _key) {}
    get value(){
        return this._object[this._key]
    }
    set value(newVal){
        this._object[this._key] = newVal
    }
}
export function toRef(object,key){
    return new ObjectRefImpl(object,key);
}
export function toRefs(object) { // object可能是数组或对象
    const ret = isArray(object) ? new Array(object.length) : {};
    for (const key in object) {
        ret[key] = toRef(object, key)
    }
    return ret;
}

3. Computed实现原理

vue3.0 中的computed实现思路和 vue2.0 源码基本一致,也是基于缓存来实现的

// packages/reactivity/ref.ts
import { effect, track, trigger } from './effect';
import { isFunction } from '@vue/shared';
class ComputedRefImpl {
    private _value;
    private _dirty = true; // 默认是脏值,不要用缓存的值
    public readonly effect;
    public readonly __v_isRef = true;
    constructor(getter, private readonly _setter) {
        this.effect = effect(getter, {
            lazy: true, // 计算属性特性,默认不执行
            scheduler: () => {
                if (!this._dirty) { // 依赖属性变化时
                    this._dirty = true; // 标记为脏值,触发视图更新
                    trigger(this, 'set', 'value');
                }
            }
        })
    }
    get value() {    // 计算属性也要收集依赖 计算属性本身就是一个effect
        if (this._dirty) {
            // 取值时执行effect
            this._value = this.effect();
            this._dirty = false;
        }
        track(this,  TrackOpTypes.GET ,'value'); // 进行属性依赖收集
        return this._value
    }
    set value(newValue) {
        this._setter(newValue);
    }
}

// vue2 和 vue3 的computed原理是不一样的
export function computed(getterOrOptions) {
    let getter;
    let setter;
    if (isFunction(getterOrOptions)) { // computed两种写法
        getter = getterOrOptions;
        setter = () => {
            console.warn('computed value is readonly')
        }
    } else {
        getter = getterOrOptions.get;
        setter = getterOrOptions.set;
    }
    return new ComputedRefImpl(getter, setter)
}