Effect 原理解析 与 实现
引言:
vue、react 框架的核心都是数据驱动视图也就是model => view,实现的核心也就是 数据响应。
主要就三步:
创建响应式的数据 defineProperty、pxoxy。这样使用、修改数据的事件我们都能捕捉到
在使用响应式的数据时收集依赖,把跟该数据相关的副作用都储存起来
-
在修改响应式的数据时触发依赖,执行相关的副作用
{{msg}}
一、effect:副作用函数
1.类似于vue2.0中watch 的升级版,如果函数中用到的响应式的数据发生了变化,则会执行该函数
// Effect 的简单应用
const component = defineComponent({
name: 'zhenAPP',
template: `
`,
setup(props) {
const data = reactive({
count: 0,
});
console.log('-----------创建reactive------------')
console.log('创建reactive对象:', data)
const addHandler = () => {
data.count++;
};
effect(() => {
console.log('1',data.count)
});
return {
addHandler,
};
},
});
// 在 Vue.js 3.0 中,初始化一个应用的方式如下
import { createApp } from 'vue'
import App from './app'
const app = createApp(App)
app.mount('#app')
// 设置并运行带副作用的渲染函数
setupRenderEffect(instance, initialVNode, container, anchor, parentSuspense ...)
二、proxy 与reflect
Object.defineProperty API 的一些缺点:
1. 不能监听对象属性新增和删除;
2. 初始化阶段递归执行 Object.defineProperty 带来的性能负担。
const p = new Proxy(target, handlerObject)
handlerObject = {
get(target,key,receiver){ // receiver 可以理解成改变get函数中的this指向,默认就是handlerObject
},
set(target,key,value,receiver){
}
}
vue3源码的调试方法:
- clone vue-next
- npm run dev
- 新建html文件,引用打包的js
vue-demo
三、响应式api reactive的实现
// 0 reactactive模块的存储变量:reactiveMap
// 作用1:维护整个应用数据代理,防止对同一个对象重复代理,比如父子组件共享某个响应数据
// 作用2:weakmap 本身的优势,这样某个组件卸载之后,组件上的响应数据也会被删除,reactiveMap也会删除响应数据。防止内存泄露
export const reactiveMap = new WeakMap()
// 1.reactive 方法
export function reactive(target: object) {
// ...排除不能代理的一些情况
return createReactiveObject(
target,
mutableHandlers,
)
}
// 2.createReactiveObject proxy代理
function createReactiveObject(
target: Target,
baseHandlers: ProxyHandler,
) {
// 代理去重 target already has corresponding Proxy
const proxyMap = reactiveMap
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
// 利用proxy生成响应式对象
const proxy = new Proxy(
target,
mutableHandlers
)
// 存入map
proxyMap.set(target, proxy)
return proxy
}
// 3.baseHandlers
const mutableHandlers = {
get: createGetter(),
set: createSetter(),
}
// 4. get 函数的代理:调用了track 方法
function createGetter(isReadonly = false, shallow = false) {
return function get(target: Target, key: string | symbol, receiver: object) {
const res = Reflect.get(target, key, receiver)
//重点: track 实现依赖收集。effect 中接下来会分析
track(target, TrackOpTypes.GET, key)
if (isObject(res)) {
// 深度代理
return reactive(res)
}
return res
}
}
// 5. set 函数的代理:调用了trigger方法
function createSetter(shallow = false) {
return function set(
target: object,
key: string | symbol,
value: unknown,
receiver: object
): boolean {
const oldValue = (target as any)[key]
if (!shallow) {
// 如果value本身是响应对象,把他变成普通对象
// 对应get中 isObject(res),方便统一处理
// 这也是vue3与vue2 不同的地方
// { person: {name:'tom'} }
// vue2在代理的时候,两层都会代理
// vue3在代理的时候,只代理第一层,在使用到person的时候才会代理第二层
value = toRaw(value)
} else {
// in shallow mode, objects are set as-is regardless of reactive or not
}
const result = Reflect.set(target, key, value, receiver)
// 防止原型链的影响--ppt
if (target === toRaw(receiver)) {
// 重点:在改变响应对象的值的时候,调用trigger 触发响应
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
return result
}
}
三、Effect的依赖收集与响应触发 (分-总-分-问题)
// 1. Effct 函数定义 与 局部变量缓存
export interface ReactiveEffect {
(): T
_isEffect: true
id: number
active: boolean // active是effect激活的开关,打开会收集依赖,关闭会导致收集依赖无效
raw: () => T // 原始监听函数
deps: Array // 存储依赖的deps
options: ReactiveEffectOptions
allowRecurse: boolean
}
type Dep = Set
type KeyToDepMap = Map
// 应用中 响应对象对应的 KeyToDepMap
const targetMap = new WeakMap()
// 当前执行的effect
let activeEffect: ReactiveEffect | undefined
// 执行中的effect栈
const effectStack: ReactiveEffect[] = []
// 2. Effct
export function effect(
fn: () => T,
options: ReactiveEffectOptions = EMPTY_OBJ
): ReactiveEffect {
// fn已经是一个effect函数了,利用fn.raw重新创建effect
if (isEffect(fn)) {
fn = fn.raw
}
// 创建监听函数
const effect = createReactiveEffect(fn, options)
if (!options.lazy) {
effect()
}
return effect
}
// 3.createReactiveEffect
function createReactiveEffect(
fn: () => T,
options: ReactiveEffectOptions
): ReactiveEffect {
const effect = function reactiveEffect(): unknown {
// 防止在effect(() => {data.count++}),造成循环引用
if (!effectStack.includes(effect)) {
cleanup(effect) // effect.deps = []
try {
effectStack.push(effect)
activeEffect = effect
// 调用原始函数时,如果响应式数据取值了
// 会触发这个响应式对象的getter,getter里面就会调用track方法收集依赖
return fn()
} finally {
effectStack.pop()
// 指向最后一个effect:
activeEffect = effectStack[effectStack.length - 1]
}
}
} as ReactiveEffect
effect.id = uid++
effect.allowRecurse = !!options.allowRecurse
effect._isEffect = true
effect.active = true
effect.raw = fn
effect.deps = []
effect.options = options
return effect
}
// 4.track 函数:收集依赖
export function track(target: object, type: TrackOpTypes, key: unknown) {
if (!shouldTrack || activeEffect === undefined) {
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)
}
}
// 5. trigger函数:触发依赖
export function trigger(
target: object,
type: TriggerOpTypes,
key?: unknown,
newValue?: unknown,
oldValue?: unknown,
oldTarget?: Map | Set
) {
const depsMap = targetMap.get(target)
if (!depsMap) {
// never been tracked
return
}
// 确定需要触发的依赖 set
const effects = new Set()
const add = (effectsToAdd: Set | undefined) => {
if (effectsToAdd) {
effectsToAdd.forEach(effect => {
if (effect !== activeEffect || effect.allowRecurse) {
effects.add(effect)
}
})
}
}
if (key !== void 0) {
add(depsMap.get(key))
}
console.log('count的Effects', effects)
const run = (effect: ReactiveEffect) => {
// 如果传入自定义调度器则执行自定义的,可以扩展effect执行
if (effect.options.scheduler) {
effect.options.scheduler(effect)
} else {
effect()
}
}
effects.forEach(run)
}