Effect 原理解析 與 實(shí)現(xiàn)
引言:
vue部凑、react 框架的核心都是數(shù)據(jù)驅(qū)動(dòng)視圖也就是model => view,實(shí)現(xiàn)的核心也就是 數(shù)據(jù)響應(yīng)市栗。
主要就三步:
創(chuàng)建響應(yīng)式的數(shù)據(jù) defineProperty谓苟、pxoxy诉瓦。這樣使用茬腿、修改數(shù)據(jù)的事件我們都能捕捉到
在使用響應(yīng)式的數(shù)據(jù)時(shí)收集依賴聚至,把跟該數(shù)據(jù)相關(guān)的副作用都儲(chǔ)存起來(lái)
-
在修改響應(yīng)式的數(shù)據(jù)時(shí)觸發(fā)依賴,執(zhí)行相關(guān)的副作用
<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 的升級(jí)版固该,如果函數(shù)中用到的響應(yīng)式的數(shù)據(jù)發(fā)生了變化锅减,則會(huì)執(zhí)行該函數(shù)
// Effect 的簡(jiǎn)單應(yīng)用
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對(duì)象:', data)
const addHandler = () => {
data.count++;
};
effect(() => {
console.log('1',data.count)
});
return {
addHandler,
};
},
});
// 在 Vue.js 3.0 中,初始化一個(gè)應(yīng)用的方式如下
import { createApp } from 'vue'
import App from './app'
const app = createApp(App)
app.mount('#app')
// 設(shè)置并運(yùn)行帶副作用的渲染函數(shù)
setupRenderEffect(instance, initialVNode, container, anchor, parentSuspense ...)
二伐坏、proxy 與reflect
Object.defineProperty API 的一些缺點(diǎn):
1. 不能監(jiān)聽對(duì)象屬性新增和刪除怔匣;
2. 初始化階段遞歸執(zhí)行 Object.defineProperty 帶來(lái)的性能負(fù)擔(dān)。
const p = new Proxy(target, handlerObject)
handlerObject = {
get(target,key,receiver){ // receiver 可以理解成改變get函數(shù)中的this指向桦沉,默認(rèn)就是handlerObject
},
set(target,key,value,receiver){
}
}
vue3源碼的調(diào)試方法:
- 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>
三、響應(yīng)式api reactive的實(shí)現(xiàn)
// 0 reactactive模塊的存儲(chǔ)變量:reactiveMap
// 作用1:維護(hù)整個(gè)應(yīng)用數(shù)據(jù)代理纯露,防止對(duì)同一個(gè)對(duì)象重復(fù)代理剿骨,比如父子組件共享某個(gè)響應(yīng)數(shù)據(jù)
// 作用2:weakmap 本身的優(yōu)勢(shì),這樣某個(gè)組件卸載之后埠褪,組件上的響應(yīng)數(shù)據(jù)也會(huì)被刪除浓利,reactiveMap也會(huì)刪除響應(yīng)數(shù)據(jù)。防止內(nèi)存泄露
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生成響應(yīng)式對(duì)象
const proxy = new Proxy(
target,
mutableHandlers
)
// 存入map
proxyMap.set(target, proxy)
return proxy
}
// 3.baseHandlers
const mutableHandlers = {
get: createGetter(),
set: createSetter(),
}
// 4. get 函數(shù)的代理:調(diào)用了track 方法
function createGetter(isReadonly = false, shallow = false) {
return function get(target: Target, key: string | symbol, receiver: object) {
const res = Reflect.get(target, key, receiver)
//重點(diǎn): track 實(shí)現(xiàn)依賴收集钞速。effect 中接下來(lái)會(huì)分析
track(target, TrackOpTypes.GET, key)
if (isObject(res)) {
// 深度代理
return reactive(res)
}
return res
}
}
// 5. set 函數(shù)的代理:調(diào)用了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本身是響應(yīng)對(duì)象贷掖,把他變成普通對(duì)象
// 對(duì)應(yīng)get中 isObject(res),方便統(tǒng)一處理
// 這也是vue3與vue2 不同的地方
// { person: {name:'tom'} }
// vue2在代理的時(shí)候,兩層都會(huì)代理
// vue3在代理的時(shí)候渴语,只代理第一層苹威,在使用到person的時(shí)候才會(huì)代理第二層
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)) {
// 重點(diǎn):在改變響應(yīng)對(duì)象的值的時(shí)候屠升,調(diào)用trigger 觸發(fā)響應(yīng)
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
return result
}
}
三狭郑、Effect的依賴收集與響應(yīng)觸發(fā) (分-總-分-問題)
// 1. Effct 函數(shù)定義 與 局部變量緩存
export interface ReactiveEffect<T = any> {
(): T
_isEffect: true
id: number
active: boolean // active是effect激活的開關(guān),打開會(huì)收集依賴汇在,關(guān)閉會(huì)導(dǎo)致收集依賴無(wú)效
raw: () => T // 原始監(jiān)聽函數(shù)
deps: Array<Dep> // 存儲(chǔ)依賴的deps
options: ReactiveEffectOptions
allowRecurse: boolean
}
type Dep = Set<ReactiveEffect>
type KeyToDepMap = Map<any, Dep>
// 應(yīng)用中 響應(yīng)對(duì)象對(duì)應(yīng)的 KeyToDepMap
const targetMap = new WeakMap<any, KeyToDepMap>()
// 當(dāng)前執(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)是一個(gè)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
// 調(diào)用原始函數(shù)時(shí),如果響應(yīng)式數(shù)據(jù)取值了
// 會(huì)觸發(fā)這個(gè)響應(yīng)式對(duì)象的getter糕殉,getter里面就會(huì)調(diào)用track方法收集依賴
return fn()
} finally {
effectStack.pop()
// 指向最后一個(gè)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) => {
// 如果傳入自定義調(diào)度器則執(zhí)行自定義的亩鬼,可以擴(kuò)展effect執(zhí)行
if (effect.options.scheduler) {
effect.options.scheduler(effect)
} else {
effect()
}
}
effects.forEach(run)
}