Vue3源码解析(V3.2.45)-响应式源码分析与实现

说明:本章内容为博主在原教程基础上添加自己的学习笔记,来源珠峰架构Vue3课程 (opens new window),版权归原作者所有。

# 响应式源码分析与实现

# reactivity包结构

  • index.ts:这是包的入口文件,负责暴露(导出)包下所有的api接口给外部调用
  • reactive.ts:处理响应式,核心是proxy,柯里化函数转换,高阶函数,函数是否是只读,是否是深度处理
  • effect.ts:核心api,响应式核心原理就是每次get对象属性的时候收集effect,每次set对象属性的时候获取effect去执行更新
  • computed.ts:实际也是一个effect,计算属性的依赖属性会搜集这个effect
    //导出方法,不实现功能

    //响应式重要api
    export {
        reactive,
        shallowReactive,
        readonly,
        shallowReadonly
    } from './reactive'

    //响应式核心api
    export {
        effect
    } from './effect'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
index.ts

高阶函数

函数的参数或者返回值是函数

# shared-index

shared

给其他包提供公用的功能

export const isObject = (value) => typeof value == 'object' && value !== null
export const extend = Object.assign
export const isArray = Array.isArray
export const isFunction = (value) => typeof value == 'function'
export const isNumber = (value) => typeof value == 'number'
export const isString = (value) => typeof value == 'string'
export const isIntegerKey = (key) => typeof parseInt(key) + '' === key
let hasOwnProperty = Object.prototype.hasOwnProperty
export const hasOwn = (target, key) => hasOwnProperty.call(target, key)
export const hasChanged = (oldValue, value) => oldValue !== value
1
2
3
4
5
6
7
8
9
10
index.ts

# reactive

提示

可读可写,代理对象所有层级属性(只要是对象)

import { isObject } from "@vue/shared"
import { reactiveHandlers, shallowReactiveHandlers, readonlyHandlers, shallowReadonlyHandlers } from "./baseHandlers"

/*
    @这四个方法表示了是不是只读,是不是深度 
*/
export function reactive(target) {
    return createReactiveObject(target, false, reactiveHandlers)//false不是只读
}

export function shallowReactive(target) {
    return createReactiveObject(target, false, shallowReactiveHandlers)//false不是只读
}

export function readonly(target) {
    return createReactiveObject(target, true, readonlyHandlers)//true是只读
}

export function shallowReadonly(target) {
    return createReactiveObject(target, true, shallowReadonlyHandlers)//true是只读
}

const reactiveMap = new WeakMap()//WeakMap会自动垃圾回收,不会造成内存泄漏,key只能是object
const readonlyMap = new WeakMap()

/*
    @四个方法根据不同的参数实现不同的逻辑,
    柯里化方,
    工厂方法,
    底层使用Proxy,
    最核心的是要拦截数据的读取和修改
*/
export function createReactiveObject(target, isReadonly = true, baseHandlers) {
    //如果传入的target不是对象,那么就无法拦截。reactive只能拦截Object类型的对象
    if (!isObject(target)) {
        return target
    }

    //按照是否只读获取到对应的map
    const proxyMap = isReadonly ? readonlyMap : reactiveMap
    //如果这个对象已经被代理过了就不要再次代理了
    const existedProxy = proxyMap.get(target)
    if (existedProxy) {
        return existedProxy
    }
    //如果这个对象还未被代理,那么就创建一个代理对象并缓存起来
    const proxy = new Proxy(target, baseHandlers)
    //将代理对象和代理缓存到map
    proxyMap.set(target, proxy)
    return proxy
}
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
reactive代码
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>test reactive api</title>
</head>

<body>
    <script src="../dist/reactivity.global.js"></script>

    <script>
        const { reactive} = VueReactivity
        let person = { name: 'yangliangxi', age: 20, sex: '男', info: { com: 'thtf', depart: { responsibility: 'programer', leader: 'wuyanjun' } } }

        let proxyPerson = reactive(person)
        console.log("01 非只读且深代理")
        console.log("非只读且深代理输出整个对象--->", proxyPerson)
        console.log("非只读且深代理输出对象的proxyPerson.age属性--->", proxyPerson.age)
        console.log("非只读且深代理输出对象的proxyPerson.info属性--->", proxyPerson.info)
        console.log("非只读且深代理输出对象的proxyPerson.info.depart属性--->", proxyPerson.info.depart)
        console.log("")
    </script>
</body>

</html>
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
reactive用例

# shallowReactive

提示

可读可写,只代理对象最外层属性(嵌套的对象不代理,输出原来的对象)

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>test shallowReactive api</title>
</head>

<body>
    <script src="../dist/reactivity.global.js"></script>

    <script>
        const { shallowReactive } = VueReactivity
        let person = { name: 'yangliangxi', age: 20, sex: '男', info: { com: 'thtf', depart: { responsibility: 'programer', leader: 'wuyanjun' } } }

        let proxyPerson = shallowReactive(person)
        console.log("02 非只读且浅代理")
        console.log("非只读且浅代理输出整个对象--->", proxyPerson)
        console.log("非只读且浅代理输出对象的proxyPerson.age属性--->", proxyPerson.age)
        console.log("非只读且浅代理输出对象的proxyPerson.info属性--->", proxyPerson.info)
        console.log("非只读且浅代理输出对象的proxyPerson.info.depart属性--->", proxyPerson.info.depart)
        console.log("")
    </script>
</body>

</html>
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
功能代码见reactive, shallowReactive用例

# readonly

提示

只读,不可以修改对象任意层级的属性值

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>test readonly api</title>
</head>

<body>
    <script src="../dist/reactivity.global.js"></script>

    <script>
        const { readonly} = VueReactivity
        let person = { name: 'yangliangxi', age: 20, sex: '男', info: { com: 'thtf', depart: { responsibility: 'programer', leader: 'wuyanjun' } } }

        let proxyPerson = readonly(person)
        console.log("03 只读")
        console.log("只读输出整个对象--->", proxyPerson)
        console.log("只读输出对象的proxyPerson.age属性--->", proxyPerson.age)
        console.log("只读输出对象的proxyPerson.info属性--->", proxyPerson.info)
        console.log("只读输出对象的proxyPerson.info.depart属性--->", proxyPerson.info.depart)
        console.log("只读修改对象的proxyPerson.age属性")
        proxyPerson.age = 200
        console.log("只读修改对象的proxyPerson.info.depart.leader属性")
        proxyPerson.info.depart.leader = 'yuanjinghui'
        console.log("")
    </script>
</body>

</html>
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
功能代码见reactive, readonly用例

# shallowReadonly

提示

浅只读,不可以修改对象最外层的属性值,嵌套的属性值可以修改

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>test shallowReadonly api</title>
</head>

<body>
    <script src="../dist/reactivity.global.js"></script>

    <script>
        const { shallowReadonly } = VueReactivity
        let person = { name: 'yangliangxi', age: 20, sex: '男', info: { com: 'thtf', depart: { responsibility: 'programer', leader: 'wuyanjun' } } }

        let proxyPerson = shallowReadonly(person)
        console.log("04 只读且浅代理")
        console.log("只读且浅代理输出整个对象--->", proxyPerson)
        console.log("只读且浅代理输出对象的proxyPerson.age属性--->", proxyPerson.age)
        console.log("只读且浅代理输出对象的proxyPerson.info属性--->", proxyPerson.info)
        console.log("只读且浅代理输出对象的proxyPerson.info.depart属性--->", proxyPerson.info.depart)
        console.log("只读且浅代理修改对象的proxyPerson.age属性")
        proxyPerson.age = 200
        console.log("只读且浅代理修改对象的proxyPerson.info.depart.leader属性")
        proxyPerson.info.depart.leader = 'yuanjinghui'
        console.log("")

    </script>
</body>

</html>
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
功能代码见reactive, shallowReadonly用例

# effect

提示

  • effect中对象的所有属性get时都会收集effect,当这个属性的值发生变化set时会重新执行effect
  • get时调用track方法,set时调用trigger方法
import { isArray, isIntegerKey, isNumber } from "@vue/shared";
import { TrackOpTypes, TriggerOpTypes } from "./operators"

let uid = 0
let activeEffect;//当前激活的effect(执行的effect)
const effectStack = []//effect栈
function createReactiveEffect(fn, options) {
    const effect = function reactiveEffect() {
        if (!effectStack.includes(effect)) {//如果栈里不存在这个函数就push进
            try {
                effectStack.push(effect)//每次先把effect入栈
                activeEffect = effect//标致当前正在执行的effect
                return fn()//这个是用户通过effect传过来的函数,一般这个方法中会包含对象属性的取值,所以会执行get方法
            }
            finally {
                effectStack.pop()//执行完effect后需要把这个effect出栈
                activeEffect = effectStack[effectStack.length - 1]//effect出栈后需要将当前激活的effect标识成栈里最后一个effect
            }
        }
    }

    effect.id = uid++//标识effect,目的是进行区分
    effect._isEffect = true//标识这个effect是响应式的
    effect.raw = fn//记录原函数
    effect.options = options//记录配置选项

    return effect
}

//让对象的某个属性收集当前它对应的effect函数
export function effect(fn, options: any = {}) {
    //需要让这个effect变成响应式的effect,可以做到数据变化重新执行
    const _effect = createReactiveEffect(fn, options)
    if (!options.lazy) {//如果不是懒模式默认会被执行一次
        _effect()
    }
    return _effect
}

/*
 @收集属性和effect的映射
*/

const targetMap = new WeakMap()
export function track(target, type: TrackOpTypes, key) {
    if (activeEffect === undefined) {//此属性不用收集依赖,因为没在effect中使用
        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)
    }

    console.log(targetMap)
}

//找属性对应的effect让其执行,支持数组和对象
export function trigger(target, type: TriggerOpTypes, key?, newValue?, oldValue?) {
    // console.log(target, type, key, newValue, oldValue, "trigger...")
    //如果这个属性没有收集过effect,那么不需要做任何处理
    const depsMap = targetMap.get(target)
    if (!depsMap) {
        return
    }

    //如果收集了effect,那么就要将所有的effect都放在一个集合中一起执行
    const effects = new Set()//set具有去重的功能

    const add = (effectsToAdd) => {
        if (effectsToAdd) {
            effectsToAdd.forEach((effect) => {
                effects.add(effect)
            })
        }
    }
    //首先看是否修改的是数组的长度
    if (key === 'length' && isArray(target)) {
        depsMap.forEach((dep, key) => {
            if (key === 'length' || key > newValue) {
                add(dep)
            }
        });
    } else {//对象情况
        if (key !== undefined) {
            add(depsMap.get(key))
        }
        switch (type) {
            case TriggerOpTypes.ADD:
                if (isArray(target) && isIntegerKey(key)) {
                    add(depsMap.get('length'))
                }
        }
    }
}
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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
effect代码
import { extend, hasChanged, hasOwn, isArray, isIntegerKey, isObject } from "@vue/shared";
import { track, trigger } from "./effect";
import { TrackOpTypes, TriggerOpTypes } from "./operators";
import { reactive, readonly } from "./reactive";

//getter:拦截获取数值
function createGetter(isReadonly = false, shallow = false) {//isReadonly = false, shallow = false表示默认的reactive既不是readonly也不是shallow  
    return function get(target, key, recevier) {//recevier是个代理对象,谁调用就代指谁
        //Proxy+Reflect:后续Object的方法会被逐步迁移到Reflect上

        const res = Reflect.get(target, key, recevier)//等价于target[key],这个操作可能失败,但是不会报异常也不会有返回结果,而Reflect.get具有操作的状态返回给使用者。

        if (!isReadonly) {
            //搜集依赖,等用户属性数据变化后执行更新,执行effect时会执行用户传入的fn函数,这样就将属性对应的effect收集起来了
            track(target, TrackOpTypes.GET, key)
        }

        if (shallow) { return res }

        if (isObject(res)) {
            return isReadonly ? readonly(res) : reactive(res)
        }

        return res
    }
}

//setter:拦截设置数值
function createSetter(shallow = false) {
    return function set(target, key, value, recevier) {
        //Proxy+Reflect:后续Object的方法会被逐步迁移到Reflect上
        const oldValue = target[key]//获取到旧值
        let hasKey = isArray(target) && isIntegerKey(key) ? Number(key) < target.length : hasOwn(target, key)//先看下target里有没有key这个属性

        if (!hasKey) {//属性有新增
            trigger(target, TriggerOpTypes.ADD, key, value)
        } else if (hasChanged(oldValue, value)) {//属性有修改
            trigger(target, TriggerOpTypes.SET, key, value, oldValue)
        }
        const res = Reflect.set(target, key, value, recevier)//等价于target[key]=value,这个操作可能失败,但是不会报异常也不会有返回结果,而Reflect.set具有操作的状态返回给使用者。

        return res
    }
}

const get = createGetter(false, false);

const shallowGet = createGetter(false, true);

const readonlyGet = createGetter(true, false);

const shallowReadonlyGet = createGetter(true, true);

const set = createSetter()

const shallowSet = createSetter(true)

/*
    @这四个对象表示4种不同的响应处理逻辑,
    实现new Proxy(target,baseHandlers)中的baseHandlers逻辑
    是不是只读
    是不是深度
*/
export const reactiveHandlers = {
    get,
    set
}

export const shallowReactiveHandlers = {
    get: shallowGet,
    set: shallowSet
}

let readonlyObj = {//提取出公共的代码
    set: (target, key) => { console.info(`只读!不能给对象${target}的属性${key}设置数据`) }
}

export const readonlyHandlers = extend({//extend是Object.assign,起到合并两个对象的作用,可以去掉重复代码
    get: readonlyGet,
}, readonlyObj)

export const shallowReadonlyHandlers = extend({
    get: shallowReadonlyGet,
}, readonlyObj)
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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
baseHandlers代码
export const enum TrackOpTypes {
    GET
}

export const enum TriggerOpTypes {
    ADD,
    SET
}
1
2
3
4
5
6
7
8
operators代码
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>test effect api</title>
</head>

<body>
    <script src="../dist/reactivity.global.js"></script>

    <script>
        const { reactive, effect } = VueReactivity
        let person = { name: 'yangliangxi', age: 20, arr: [1, 2, 3] }

        let proxyPerson = reactive(person)
        // console.log("01 非只读且深代理")
        // console.log("非只读且深代理输出整个对象--->", proxyPerson)
        // console.log("非只读且深代理输出对象的proxyPerson.age属性--->", proxyPerson.age)
        // console.log("非只读且深代理输出对象的proxyPerson.info属性--->", proxyPerson.info)
        // console.log("非只读且深代理输出对象的proxyPerson.info.depart属性--->", proxyPerson.info.depart)
        // console.log("")

        effect(() => {
            console.log(proxyPerson.arr[2], proxyPerson.arr.length)
        })

        setTimeout(() => {
            proxyPerson.arr.length = 3//修改数组的长度
        }, 2000)

    </script>
</body>

</html>
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
effect用例

# computed

实现思想

  • 计算属性是一个effect,_dirty=true触发执行
  • 计算属性的依赖属性会搜集这个effect
  • 计算属性具备依赖搜集的功能,会搜集对应的effect方法
  • 第一次执行effect方法时会取computed中的值 _dirty=true,此时如果多次执行就走缓冲
  • 计算属性依赖的属性值发生变化了,将_dirty=true,触发计算属性搜集的effect,这样就会重新计算计算属性的值
import { isFunction } from "@vue/shared";
import { isTracking, ReactiveEffect, trackEffects } from "./effect";

class ComputedRefImpl {
    public dep: Set<ReactiveEffect> = new Set<ReactiveEffect>();  //等价于this.dep=undefined
    private _dirty: boolean = true; //等价于this._dirty=true
    private __v_isRef: boolean = true; //等价于this.__v_isRef=true
    public effect: ReactiveEffect = new ReactiveEffect(() => { });//用effect包装
    private _value: any;//保存effect.run()的结果

    constructor(getter: any, public setter: any) {
        //这里将计算属性变成了effect,那么计算属性中的属性就会搜集这个effect
        //将计算属性包装成effect
        this.effect = new ReactiveEffect(getter, () => {
            //计算属性的值发生变化了,那么就不要重新执行计算属性,而是去调用此函数
            if (!this._dirty) {
                this._dirty = true
                //triggerEffects(this.dep)
            }
        })
    }

    get value() {//如果取值的时候会触发get方法
        if (isTracking()) {
            trackEffects(this.dep)
        }

        if (this._dirty) {
            this._value = this.effect.run()//将结果缓冲在this._value中,不用每次都run
            this._dirty = false
        }
        return this._value
    }

    set value(newValue) {//如果修改属性的值就触发这个set方法
        this.setter(newValue)
    }
}

export function computed(getterOrOptions: unknown) {
    const onlyGetter = isFunction(getterOrOptions)
    let setter;
    let getter;

    if (onlyGetter) {
        getter = getterOrOptions
        setter = () => { }
    } else {
        getter = (getterOrOptions as any).get
        setter = (getterOrOptions as any).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
46
47
48
49
50
51
52
53
54

# ref

import { isObject } from "@vue/shared"
import { isTracking, ReactiveEffect, trackEffects, triggerEffects } from "./effect"
import { reactive, toReactive } from "./reactive"

class RefImpl {
    public dep: Set<ReactiveEffect> = new Set<ReactiveEffect>
    public __v_isRef: boolean = true
    public _value: any
    constructor(public _rawValue: any) {
        this._value = toReactive(_rawValue)
    }

    //类是属性访问器最终会变成defineProperty
    get value() {//取值的时候进行依赖搜集
        if (isTracking()) {
            trackEffects(this.dep)
        }
        return this._value
    }
    set value(newValue) {//赋值的时候触发更新
        if (newValue !== this._rawValue) {
            this._rawValue = newValue
            this._value = reactive(newValue)
            //triggerEffects(this.dep)
        }
    }
}

function createRef(value: any) {
    return new RefImpl(value)
}

export function ref(value: any) {
    return createRef(value)
}
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
上次更新: 2025/02/10, 20:20:37
最近更新
01
Git问题集合
01-29
02
安装 Nginx 服务器
01-25
03
安装 Docker 容器
01-25
更多文章>
×
×