effect函数的内部工作原理
effect函数的内部工作原理
effect函数是Vue3响应式系统的核心,它负责创建响应式副作用,当依赖的响应式数据变化时自动重新执行。理解effect的工作原理对深入掌握Vue3响应式机制至关重要。
基本使用示例
import { reactive, effect } from 'vue'
const state = reactive({ count: 0 })
effect(() => {
console.log('count changed:', state.count)
})
state.count++ // 触发effect重新执行
核心数据结构
effect内部维护几个关键数据结构:
- activeEffect:当前正在运行的effect引用
- effectStack:effect调用栈
- targetMap:存储所有响应式对象及其属性的依赖关系
type Dep = Set<ReactiveEffect>
type KeyToDepMap = Map<any, Dep>
const targetMap = new WeakMap<any, KeyToDepMap>()
let activeEffect: ReactiveEffect | undefined
const effectStack: ReactiveEffect[] = []
effect创建过程
当调用effect(fn)时:
- 创建ReactiveEffect实例
- 立即执行effect.run()
- 返回runner函数用于手动控制
class ReactiveEffect {
constructor(
public fn: () => T,
public scheduler?: () => void
) {}
run() {
if (!effectStack.includes(this)) {
try {
effectStack.push((activeEffect = this))
return this.fn()
} finally {
effectStack.pop()
activeEffect = effectStack[effectStack.length - 1]
}
}
}
}
function effect(fn) {
const _effect = new ReactiveEffect(fn)
_effect.run()
const runner = _effect.run.bind(_effect)
runner.effect = _effect
return runner
}
依赖收集机制
依赖收集发生在属性访问时:
- track函数建立响应式对象属性与effect的映射关系
- 使用WeakMap+Map+Set三级结构存储依赖
function track(target, type, key) {
if (!activeEffect) 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)
activeEffect.deps.push(dep)
}
}
触发更新机制
当响应式数据变化时:
- trigger函数查找所有依赖该属性的effect
- 根据情况直接执行或通过调度器执行
function trigger(target, type, key) {
const depsMap = targetMap.get(target)
if (!depsMap) return
const effects = new Set<ReactiveEffect>()
const addEffects = (dep: Dep) => {
dep.forEach(effect => {
if (effect !== activeEffect) {
effects.add(effect)
}
})
}
if (key !== void 0) {
addEffects(depsMap.get(key))
}
// 处理数组length等特殊情况
// ...
effects.forEach(effect => {
if (effect.scheduler) {
effect.scheduler()
} else {
effect.run()
}
})
}
嵌套effect处理
effect支持嵌套调用,通过effectStack维护正确的父子关系:
effect(() => {
console.log('outer effect:', state.count)
effect(() => {
console.log('inner effect:', state.double)
})
})
调度器机制
effect支持自定义调度逻辑:
effect(() => {
console.log(state.count)
}, {
scheduler(effect) {
// 自定义调度逻辑
requestAnimationFrame(effect.run)
}
})
停止effect
通过返回的runner可以停止effect:
const runner = effect(() => { /*...*/ })
runner.effect.stop() // 停止响应
性能优化策略
effect内部实现了多项优化:
- 避免重复收集:通过effectStack防止同一effect重复收集
- 惰性执行:调度器可以控制执行时机
- 清理机制:每次运行前清理旧依赖
function cleanup(effect) {
const { deps } = effect
for (let i = 0; i < deps.length; i++) {
deps[i].delete(effect)
}
effect.deps.length = 0
}
与computed/watch的关系
Vue3的computed和watch都是基于effect实现:
function computed(getter) {
let dirty = true
const runner = effect(getter, {
lazy: true,
scheduler() {
dirty = true
}
})
return {
get value() {
if (dirty) {
value = runner()
dirty = false
}
return value
}
}
}
响应式边界情况处理
effect需要处理各种边界情况:
- 自增操作避免无限循环
- 数组方法特殊处理
- 原型链属性访问
// 避免无限循环的例子
const state = reactive({ count: 0 })
effect(() => {
// 如果没有保护机制,这会无限循环
state.count = state.count + 1
})
调试支持
effect提供丰富的调试钩子:
effect(() => {
// ...
}, {
onTrack(e) {
debugger
},
onTrigger(e) {
debugger
}
})
与渲染系统的协作
组件渲染本质上是一个effect:
instance.update = effect(function componentEffect() {
if (!instance.isMounted) {
// 首次挂载
const subTree = render.call(proxy)
patch(null, subTree, container)
instance.isMounted = true
} else {
// 更新
const nextTree = render.call(proxy)
patch(prevTree, nextTree, container)
}
}, { scheduler: queueJob })
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:依赖收集与追踪的详细流程
下一篇:响应式代理的创建过程