从零手写Vue3响应式原理

一.响应式API实现

const { reactive, shallowReactive, readonly, shallowReadonly } = VueReactivity
let obj = { name: 'zf', age: { n: 11 } }
const state = reactive(obj)
const state = shallowReactive(obj)
const state = readonly(obj)
const state = shallowReadonly(obj)
1
2
3
4
5
6

针对不同的API创建不同的响应式对象

import {
    mutableHandlers,
    readonlyHandlers,
    shallowReactiveHandlers,
    shallowReadonlyHandlers
} from "./baseHandlers"; // 不同的拦截函数

export function reactive(target) {
    return createReactiveObject(target, false, mutableHandlers)
}

export function shallowReactive(target) {
    return createReactiveObject(target, false, shallowReactiveHandlers)
}

export function readonly(target) {
    return createReactiveObject(target, true, readonlyHandlers)
}

export function shallowReadonly(target) {
    return createReactiveObject(target, true, shallowReadonlyHandlers)
}
/**
 * 
 * @param target 拦截的目标
 * @param isReadonly 是不是仅读属性
 * @param baseHandlers 对应的拦截函数
 */
function createReactiveObject(target, isReadonly, baseHandlers) {}
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

二.shared模块实现

{
  "name": "@vue/shared",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "module": "dist/shared.esm-bundler.js",
  "buildOptions": {
    "formats": [
      "esm-bundler",
      "cjs"
    ]
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

配置tsconfig.json识别引入第三方模块

{
    "baseUrl": ".",
    "moduleResolution": "node",
    "paths": {
      "@vue/*": [
        "packages/*/src"
      ]
    }
}
1
2
3
4
5
6
7
8
9

使用yarn installshared模块注入到node_modules中

三.createReactiveObject实现

Vue3中采用proxy实现数据代理, 核心就是拦截get方法和set方法,当获取值时收集effect函数,当修改值时触发对应的effect重新执行

import { isObject } from '@vue/shared'
const reactiveMap = new WeakMap(); 
const readonlyMap = new WeakMap();
function createReactiveObject(target, isReadonly, baseHandlers) {
    // 1.如果不是对象直接返回
    if(!isObject(target)){ 
        return target
    }
    const proxyMap = isReadonly ? readonlyMap : reactiveMap; // 获取缓存对象
    const existingProxy = proxyMap.get(target);
    // 2.代理过直接返回即可
    if(existingProxy){ 
        return existingProxy;
    }
    // 3.代理的核心
    const proxy = new Proxy(target,baseHandlers); 
    proxyMap.set(target,proxy);
	// 4.返回代理对象
    return proxy; 
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

baseHandlers实现

import { isObject } from "@vue/shared";
import { reactive, readonly } from "./reactive";

const get = createGetter();
const shallowGet = createGetter(false, true);
const readonlyGet = createGetter(true);
const shallowReadonlyGet = createGetter(true, true)

const set = createSetter();
const shallowSet = createSetter(true);

/**
 * @param isReadonly 是不是仅读
 * @param shallow 是不是浅响应
 */
function createGetter(isReadonly = false, shallow = false) {
    return function get(target, key, receiver) {
        const res = Reflect.get(target, key, receiver);

        if (!isReadonly) { // 如果是仅读的无需收集依赖
            console.log('依赖收集')
        }

        if (shallow) { // 浅无需返回代理
            return res
        }

        if (isObject(res)) { // 取值时递归代理
            return isReadonly ? readonly(res) : reactive(res)
        }
        return res;
    }
}

function createSetter(shallow = false) {
    return function set(target, key, value, receiver) {
        const result = Reflect.set(target, key, value, receiver);
        return result;
    }
}

export const mutableHandlers = {
    get,
    set
};
export const readonlyHandlers = {
    get: readonlyGet,
    set(target, key) {
        console.warn(`Set operation on key "${String(key)}" failed: target is readonly.`)
        return true;
    }
};
export const shallowReactiveHandlers = {
    get: shallowGet,
    set: shallowSet
};
export const shallowReadonlyHandlers = {
    get: shallowReadonlyGet,
    set(target, key) {
        console.warn(`Set operation on key "${String(key)}" failed: target is readonly.`)
        return true;
    }
};
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

四.effect实现

实现响应式effect

export function effect(fn, options: any = {}) {
    // 创建响应式effect
    const effect = createReactiveEffect(fn, options);
    // 默认会让effect先执行一次
    if (!options.lazy) {
        effect();
    }
    return effect
}
let uid = 0;
function createReactiveEffect(fn, options) {
    // 返回响应式effect
    const effect = function reactiveEffect() {
		// todo...
    }
    effect.id = uid++;// 用于做标识的
    effect._isEffect = true; // 标识是响应式effect
    effect.raw = fn; // 记录原本的fn
    effect.deps = []; // 用于收集effect对应的相关属性
    effect.options = options;
    return effect;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

利用栈型结构存储effect,保证依赖关系

const state =reactive({name:'zf',age:12,address:'回龙观'})
effect(()=>{ // effect1      
    console.log(state.name); // 收集effect1          
    effect(()=>{ // effect2 
        console.log(state.age); // 收集effect2
    });
    console.log(state.address); // 收集effect1
})
1
2
3
4
5
6
7
8
const effect = function reactiveEffect() {
    if (!effectStack.includes(effect)) {
        try {
            effectStack.push(effect);
            activeEffect = effect; // 记录当前的effect
            return fn(); // 执行用户传递的fn -> 取值操作
        } finally {
            effectStack.pop();
            activeEffect = effectStack[effectStack.length - 1];
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

五.track依赖收集

function createGetter(isReadonly = false, shallow = false) {
    return function get(target, key, receiver) {
      	// ...
        if (!isReadonly) { // effect函数执行时,进行取值操作,让属性记住对应的effect函数
            track(target, TrackOpTypes.GET, key);
        }
    }
}
1
2
3
4
5
6
7
8
const targetMap = new WeakMap();
export function track(target, type, key) {
    if(activeEffect === undefined){ // 如果不在effect中取值,则无需记录
        return
    }
    let depsMap = targetMap.get(target);
    // WeakMap({name:'zf',age:11},{name:{Set},age:{Set}})
    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);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

六.trigger触发更新

对新增属性和修改属性做分类

function createSetter(shallow = false) {
    return function set(target, key, value, receiver) {
        const oldValue = target[key];
        const hadKey =
            isArray(target) && isIntegerKey(key) ? Number(key) < target.length : hasOwn(target, key);
        const result = Reflect.set(target, key, value, receiver);
        if (!hadKey) {
            // 新增属性
            trigger(target, TriggerOpTypes.ADD, key, value)
        } else if (hasChanged(value, oldValue)) {
            // 修改属性
            trigger(target, TriggerOpTypes.SET, key, value, oldValue)
        }
        return result;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

将需要触发的effect找到依次执行

export function trigger(target, type, key?, newValue?, oldValue?) {
    const depsMap = targetMap.get(target);
    if (!depsMap) { // 属性没有对应的effect
        return
    }
    const effects = new Set(); // 设置集合
    const add = (effectsToAdd) => {
        if (effectsToAdd) {
            effectsToAdd.forEach(effect => {
                effects.add(effect);
            })
        }
    }
    if (key === 'length' && isArray(target)) { // 如果修改的是长度
        depsMap.forEach((dep, key) => {
            // 如果有长度的依赖要更新  如果依赖的key小于设置的长度也要更新 
            if (key == 'length' || key >= newValue) {
                add(dep)
            }
        });
    } else {
        if (key !== void 0) {
            // 修改key
            add(depsMap.get(key));
        }
        switch (type) {
            case TriggerOpTypes.ADD:
                if (isArray(target)) {
                    if (isIntegerKey(key)) { // 给数组新增属性,直接触发length即可
                        add(depsMap.get('length'))
                    }
                }
                break;
            default:
                break;
        }
    }
    effects.forEach((effect: any) => {
        effect();
    })
}
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

七.实现Ref

ref本质就是通过类的属性访问器来实现的,可以将一个普通值类型进行包装。

import { hasChanged, isObject } from "@vue/shared";
import { track, trigger } from "./effect";
import { TrackOpTypes, TriggerOpTypes } from "./operations";
import { reactive } from "./reactive";

export function ref(value) { // ref Api
    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; // 标识是ref
    constructor(private _rawValue, public readonly _shallow) {
        this._value = _shallow ? _rawValue : convert(_rawValue)
    }
    get 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);
        }
    }
}
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

八.实现toRefs

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) {
    const ret = isArray(object) ? new Array(object.length) : {};
    for (const key in object) {
        ret[key] = toRef(object, key)
    }
    return ret;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

将对象中的属性转换成ref属性

九.实现Computed

computed的整体思路和Vue2.0源码基本一致,也是基于缓存来实现的。

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() {
        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);
    }
}
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)
}
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

effect.ts

 effects.forEach((effect: any) => {
     if(effect.options.scheduler){
         return effect.options.scheduler(effect); // 如果有自己提供的scheduler,则执行scheduler逻辑
     }
     effect();
 })
1
2
3
4
5
6