Effect 原理解析 與 實現(xiàn)
引言:
vue、react 框架的核心都是數(shù)據(jù)驅動視圖也就是model => view,實現(xiàn)的核心也就是 數(shù)據(jù)響應。
主要就三步:
創(chuàng)建響應式的數(shù)據(jù) defineProperty、pxoxy。這樣使用、修改數(shù)據(jù)的事件我們都能捕捉到
在使用響應式的數(shù)據(jù)時收集依賴,把跟該數(shù)據(jù)相關的副作用都儲存起來
-
在修改響應式的數(shù)據(jù)時觸發(fā)依賴,執(zhí)行相關的副作用
<template>
<div>{{msg}}</div>
</template>
<script>
export default {
data(){
return {
msg: 'hello world'
}
},
methods: {
change(){
this.msg = 'zhenganlin'
}
}
}
</script>
一、effect:副作用函數(shù)
1.類似于vue2.0中watch 的升級版,如果函數(shù)中用到的響應式的數(shù)據(jù)發(fā)生了變化,則會執(zhí)行該函數(shù)
// Effect 的簡單應用
const component = defineComponent({
name: 'zhenAPP',
template: `
<div>
<button @click="addHandler">add</button>
</div>
`,
setup(props) {
const data = reactive({
count: 0,
});
console.log('-----------創(chuàng)建reactive------------')
console.log('創(chuàng)建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')
// 設置并運行帶副作用的渲染函數(shù)
setupRenderEffect(instance, initialVNode, container, anchor, parentSuspense ...)
二、proxy 與reflect
Object.defineProperty API 的一些缺點:
1. 不能監(jiān)聽對象屬性新增和刪除;
2. 初始化階段遞歸執(zhí)行 Object.defineProperty 帶來的性能負擔。
const p = new Proxy(target, handlerObject)
handlerObject = {
get(target,key,receiver){ // receiver 可以理解成改變get函數(shù)中的this指向,默認就是handlerObject
},
set(target,key,value,receiver){
}
}
vue3源碼的調試方法:
- clone vue-next
- npm run dev
- 新建html文件,引用打包的js
<!DOCTYPE html>
<html>
<head>
<title>vue-demo</title>
</head>
<body>
<div id="app"></div>
<script src="./packages/vue/dist/vue.global.js"></script>
<script>
const { defineComponent, createApp, reactive, toRefs, watchEffect } = Vue;
console.log(Vue)
const component = defineComponent({
template: `
<div>
{{ data.count }}
<button @click="addHandler">add</button>
</div>
`,
setup(props) {
const data = reactive({
count: 0,
person: {
name: 'zhenganlin'
}
});
console.log('創(chuàng)建reactive: ', data)
const addHandler = () => {
data.count++;
data.person = {age:25}
};
// watchEffect(() => {
// console.log(data.count)
// });
return {
data,
addHandler,
};
},
});
createApp(component).mount(document.querySelector('#app'));
</script>
</body>
</html>
三、響應式api reactive的實現(xiàn)
// 0 reactactive模塊的存儲變量:reactiveMap
// 作用1:維護整個應用數(shù)據(jù)代理,防止對同一個對象重復代理,比如父子組件共享某個響應數(shù)據(jù)
// 作用2:weakmap 本身的優(yōu)勢,這樣某個組件卸載之后,組件上的響應數(shù)據(jù)也會被刪除,reactiveMap也會刪除響應數(shù)據(jù)。防止內存泄露
export const reactiveMap = new WeakMap<Target, any>()
// 1.reactive 方法
export function reactive(target: object) {
// ...排除不能代理的一些情況
return createReactiveObject(
target,
mutableHandlers,
)
}
// 2.createReactiveObject proxy代理
function createReactiveObject(
target: Target,
baseHandlers: ProxyHandler<any>,
) {
// 代理去重 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 函數(shù)的代理:調用了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 實現(xiàn)依賴收集。effect 中接下來會分析
track(target, TrackOpTypes.GET, key)
if (isObject(res)) {
// 深度代理
return reactive(res)
}
return res
}
}
// 5. set 函數(shù)的代理:調用了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),方便統(tǒng)一處理
// 這也是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 觸發(fā)響應
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
return result
}
}
三、Effect的依賴收集與響應觸發(fā) (分-總-分-問題)
// 1. Effct 函數(shù)定義 與 局部變量緩存
export interface ReactiveEffect<T = any> {
(): T
_isEffect: true
id: number
active: boolean // active是effect激活的開關,打開會收集依賴,關閉會導致收集依賴無效
raw: () => T // 原始監(jiān)聽函數(shù)
deps: Array<Dep> // 存儲依賴的deps
options: ReactiveEffectOptions
allowRecurse: boolean
}
type Dep = Set<ReactiveEffect>
type KeyToDepMap = Map<any, Dep>
// 應用中 響應對象對應的 KeyToDepMap
const targetMap = new WeakMap<any, KeyToDepMap>()
// 當前執(zhí)行的effect
let activeEffect: ReactiveEffect | undefined
// 執(zhí)行中的effect棧
const effectStack: ReactiveEffect[] = []
// 2. Effct
export function effect<T = any>(
fn: () => T,
options: ReactiveEffectOptions = EMPTY_OBJ
): ReactiveEffect<T> {
// fn已經(jīng)是一個effect函數(shù)了,利用fn.raw重新創(chuàng)建effect
if (isEffect(fn)) {
fn = fn.raw
}
// 創(chuàng)建監(jiān)聽函數(shù)
const effect = createReactiveEffect(fn, options)
if (!options.lazy) {
effect()
}
return effect
}
// 3.createReactiveEffect
function createReactiveEffect<T = any>(
fn: () => T,
options: ReactiveEffectOptions
): ReactiveEffect<T> {
const effect = function reactiveEffect(): unknown {
// 防止在effect(() => {data.count++}),造成循環(huán)引用
if (!effectStack.includes(effect)) {
cleanup(effect) // effect.deps = []
try {
effectStack.push(effect)
activeEffect = effect
// 調用原始函數(shù)時,如果響應式數(shù)據(jù)取值了
// 會觸發(fā)這個響應式對象的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 函數(shù):收集依賴
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函數(shù):觸發(fā)依賴
export function trigger(
target: object,
type: TriggerOpTypes,
key?: unknown,
newValue?: unknown,
oldValue?: unknown,
oldTarget?: Map<unknown, unknown> | Set<unknown>
) {
const depsMap = targetMap.get(target)
if (!depsMap) {
// never been tracked
return
}
// 確定需要觸發(fā)的依賴 set
const effects = new Set<ReactiveEffect>()
const add = (effectsToAdd: Set<ReactiveEffect> | 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) => {
// 如果傳入自定義調度器則執(zhí)行自定義的,可以擴展effect執(zhí)行
if (effect.options.scheduler) {
effect.options.scheduler(effect)
} else {
effect()
}
}
effects.forEach(run)
}