目录
- 引言
- 举个例子
- [Proxy 是什么](#Proxy 是什么)
- [ReactiveEffect 是什么](#ReactiveEffect 是什么)
- [track 是什么(记录依赖)](#track 是什么(记录依赖))
-
- [track 的作用](#track 的作用)
- [trigger 是什么(触发更新)](#trigger 是什么(触发更新))
- 完整流程图
- [为什么 WeakMap → Map → Set](#为什么 WeakMap → Map → Set)
- [computed 怎么利用这套机制](#computed 怎么利用这套机制)
- [watchEffect 怎么利用这套机制](#watchEffect 怎么利用这套机制)
- [watch 怎么利用这套机制](#watch 怎么利用这套机制)
- Vue3响应式系统最核心的一张图
引言
Vue3 的响应式系统本质上只有一句话:
数据被读取时记录依赖(track),数据被修改时通知依赖(trigger)
整个流程:
javascript
响应式对象
│
▼
Proxy
│
├── get -> track()
│
└── set -> trigger()
│
▼
ReactiveEffect
│
▼
重新执行
举个例子
例如:
javascript
const state = reactive({
count: 0
})
watchEffect(() => {
console.log(state.count)
})
state.count++
输出:
javascript
0
1
为什么呢?Vue到底做了什么呢?
实际上:
javascript
读取 count
↓
记录依赖
修改 count
↓
找到依赖
执行依赖
Proxy 是什么
vue2使用:Object.defineProperty()
vue3使用:proxy
例如:
javascript
const obj = {
count: 0
}
const proxy = new Proxy(obj, {
get(target, key) {
console.log("读取")
return target[key]
},
set(target, key, value) {
console.log("修改")
target[key] = value
return true
}
})
读取 proxy.count,输出:读取
修改 proxy.count = 1, 输出:修改
javascript
Proxy
│
├── get
│
└── set
vue就是利用这两个拦截器!!!!!!
ReactiveEffect 是什么
看一个最简单例子:
javascript
function effect() {
console.log(state.count)
}
问题来了,vue怎么知道state.count变化时要重新执行 effect?
必须建立联系。
例如:
javascript
count
│
▼
effect
这个联系就是:依赖关系 (Dependency)
Vue内部:
javascript
class ReactiveEffect {
run() {
fn()
}
}
简化后:
javascript
const effect = new ReactiveEffect(
() => {
console.log(state.count)
}
)
本质:ReactiveEffect = 包装后的副作用函数
例如:
javascript
watchEffect(() => {
console.log(state.count)
})
内部其实类似:
javascript
new ReactiveEffect(() => {
console.log(state.count)
})
track 是什么(记录依赖)
例如:
javascript
const state = reactive({
count: 0
})
执行:
javascript
watchEffect(() => {
console.log(state.count)
})
内部:
javascript
effect.run()
开始执行:
javascript
console.log(state.count)
读取:
javascript
state.count
触发:
javascript
Proxy.get()
此时vue发现:当前正在执行 effect,于是执行:
javascript
track(target, key)
即:
javascript
track(state, "count")
track 的作用
记录谁依赖了这个属性
例如:
javascript
state.count
│
▼
effect
那么vue就要保存:count -> effect的关系。
内部数据结构:WeakMap
javascript
WeakMap
│
├── state对象
│
└── Map
│
├── count
│ │
│ ▼
│ Set(effect)
│
└── name
│
▼
Set(effect)
源码思想:
javascript
targetMap = WeakMap<
target,
Map<
key,
Set<effect>
>
>
例如:state.count 被effect使用,就会存储:
javascript
WeakMap
│
▼
state
│
▼
count
│
▼
effect
这一步就叫做:依赖收集/track
trigger 是什么(触发更新)
state.count++发生了什么?
执行:
javascript
Proxy.set()
触发:
javascript
trigger(state, "count")
vue去依赖表查找:
javascript
state
│
▼
count
│
▼
Set(effect)
找到effect,然后执行effect.run()
重新执行:
javascript
console.log(state.count)
于是输出1
这一步就叫 触发更新,也就是trigger
完整流程图
javascript
watchEffect(() => {
console.log(state.count)
})
javascript
effect.run()
│
▼
读取 count
│
▼
Proxy.get
│
▼
track()
│
▼
记录:
count -> effect
修改:
javascript
state.count++
javascript
Proxy.set
│
▼
trigger()
│
▼
找到:
count -> effect
│
▼
effect.run()
│
▼
重新执行
为什么 WeakMap → Map → Set
javascript
targetMap = WeakMap<
target,
Map<
key,
Set<effect>
>
>
javascript
WeakMap
│
├── state对象
│
└── Map
│
├── count
│ │
│ ▼
│ Set(effect)
│
└── name
│
▼
Set(effect)
第一层 WeakMap
保存对象:
javascript
const obj1 = {}
const obj2 = {}
需要:
javascript
obj1
└── count
obj2
└── name
所以WeakMap,key:target对象
第二层Map
保存属性:
javascript
state.count
state.name
需要区分name,所以Map
第三层Set
保存多个 Effect
javascript
watchEffect(() => {
console.log(state.count)
})
watchEffect(() => {
document.title = state.count
})
两个 effect,结构:
javascript
count
│
▼
Set
├── effect1
└── effect2
所以是set,防止重复
computed 怎么利用这套机制
javascript
const double = computed(() => {
return count.value * 2
})
内部也是:
javascript
new ReactiveEffect(...)
依赖:
javascript
count
│
▼
computedEffect
count变化:
javascript
trigger()
│
▼
computedEffect
重新计算。
所以说,
computed、watch、watchEffect底层全部都是ReactiveEffect
watchEffect 怎么利用这套机制
javascript
watchEffect(() => {
console.log(count.value)
})
第一次:
javascript
effect.run()
│
▼
读取 count
│
▼
track()
记录:count -> effect
修改:
javascript
count.value++
执行:
javascript
trigger()
│
▼
effect.run()
重新执行。
watch 怎么利用这套机制
watch的会更高级一点
javascript
watch(
() => state.count,
(newVal, oldVal) => {}
)
内部也会创建ReactiveEffect
监听:
javascript
() => state.count
读取:
javascript
state.count
│
▼
track()
建立:
javascript
count -> effect
变化:
javascript
trigger()
执行scheduler -> 执行callback
Vue3响应式系统最核心的一张图
javascript
reactive()
│
▼
Proxy
┌────────────┐
│ │
▼ ▼
get() set()
│ │
▼ ▼
track() trigger()
│ │
▼ ▼
收集依赖 找到依赖
│ │
▼ ▼
ReactiveEffect.run()
│
▼
执行副作用
着重看:
javascript
packages/reactivity/src/
│
├── reactive.ts
├── baseHandlers.ts
├── effect.ts
├── dep.ts
├── computed.ts
├── watch.ts
理解顺序
javascript
Proxy
↓
track
↓
trigger
↓
ReactiveEffect
↓
computed
↓
watchEffect
↓
watch
Vue3 的响应式系统本质上就是一个"发布-订阅系统"的高级实现。
javascript
track() = 订阅
trigger() = 发布
ReactiveEffect = 订阅者
Proxy = 负责监听数据访问和修改