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
2
3
4
5
6
7
8
9
10
11
12
13
14
高阶函数
函数的参数或者返回值是函数
# 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
2
3
4
5
6
7
8
9
10
# 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
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
<!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
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
# 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
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
# 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
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
# 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
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
# 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
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
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
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
export const enum TrackOpTypes {
GET
}
export const enum TriggerOpTypes {
ADD,
SET
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
<!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
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
# 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
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
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
编辑 (opens new window)
上次更新: 2025/02/10, 20:20:37