vue3 ref和computed的简单实现

62 阅读1分钟

接上一篇文章# vue3 reactive和watchEffect的简单实现

ref的实现

import { isTracking, reactive, trackEffect, triggerEffect } from "./index";

class RefImpl {
    _value: any;
    deps;
    __v_isRef = true;
    constructor(raw) {
        this.deps = new Set();
        this._value = convert(raw);
    }

    get value() {
        if (isTracking()) {
            trackEffect(this.deps);
        }
        return this._value;
    }

    set value(value) {
        if (value === this._value) return;
        this._value = convert(value);
        triggerEffect(this.deps);
    }
}

const convert = data => {
    return typeof data === 'object' ? reactive(data) : data;
}

export const ref = (raw) => {
    return new RefImpl(raw);
}

export const isRef = (raw) => {
    return raw.__v_isRef ? true : false
}

export const unRef = (raw) => {
    return isRef(raw) ? raw.value : raw;
}

ref的单测


describe('ref', () => {

    it('happy path', () => {

        const a = ref(1);

        expect(a.value).toBe(1);

    })

    it('should be reactive', () => {

        const a = ref(1);
        let dummy;
        let calls = 0;
        effect(() => {
            calls++;
            dummy = a.value;
        })

        expect(dummy).toBe(a.value);
        expect(calls).toBe(1);

        a.value = 2;

        expect(calls).toBe(2);
        expect(calls).toBe(2);

        a.value = 2;
        expect(calls).toBe(2);
        expect(calls).toBe(2);
        
    })

    it('should make nested properties reactive', () => {
        const a = ref({ count: 1 });
        let dummy;
        effect(() => {
            dummy = a.value.count;
        })
        expect(dummy).toBe(1)
        a.value.count = 2;
        expect(dummy).toBe(2)
    })

    it('isRef', () => {
        const a = ref(1);
        const user = reactive({ foo:1 });
        expect(isRef(a)).toBe(true);
        expect(isRef(true)).toBe(false);
        expect(isRef(user)).toBe(false);
    })

    it('unRef', () => {
        const a = ref(1);
        expect(unRef(a)).toBe(1);
    })

})

computed的实现

import { ReactiveEffect } from "./index";

class ComputedImpl {
    _dirty: any = true;

    rawValue;
    activeEffect: ReactiveEffect;
    constructor(private getter) {
        this.activeEffect = new ReactiveEffect(getter, () => {
            this._dirty = true;
        });
    }

    get value() {
        if (this._dirty) {
            this.rawValue = this.activeEffect.run();
            this._dirty = false;
        }
        return this.rawValue;
    }
 
}   

export const computed = (getter) => {
    return new ComputedImpl(getter);
}

computed的单测

import { computed } from "../computed";
import { reactive } from "../index"

describe('computed', () => {

    it('happy path', () => {

        const user = reactive({
            age: 10
        })

        const age = computed(() => user.age);

       
        expect(age.value).toBe(10);

    })


    it('should compute lazily', () => {
         
        const value = reactive({ foo: 1 });

        const getter = jest.fn(() => {
            return value.foo
        })

        const cValue = computed(getter);

        expect(getter).not.toHaveBeenCalled();

        expect(cValue.value).toBe(1)
        expect(getter).toHaveBeenCalledTimes(1);

        cValue.value
        expect(getter).toHaveBeenCalledTimes(1);

        value.foo = 2;
        // expect(getter).toHaveBeenCalledTimes(1);
        // expect(cValue.value).toBe(2)

    })

})