一.reactive與effect功能
reactive
方法會(huì)將對(duì)象變成proxy
對(duì)象, effect
中使用reactive
對(duì)象時(shí)會(huì)進(jìn)行依賴(lài)收集,稍后屬性變化時(shí)會(huì)重新執(zhí)行effec
t函數(shù)。
<div id="app"></div>
<script type="module">
import {
reactive,
effect,
} from "/node_modules/@vue/reactivity/dist/reactivity.esm-browser.js";
// reactive創(chuàng)建一個(gè)響應(yīng)式對(duì)象
// effect 副作用函數(shù),默認(rèn)執(zhí)行一次,數(shù)據(jù)變化后再次執(zhí)行
const state = reactive({ name: "orange", age: 18 });
effect(() => {
document.getElementById("app").innerHTML = state.name;
});
console.log(state);
setTimeout(() => {
state.name = "apple";
}, 2000);
</script>
二.reactive與effect實(shí)現(xiàn)
1.實(shí)現(xiàn)reactive
1.1 get、set基礎(chǔ)實(shí)現(xiàn)
// reactive.ts
import { isObject } from "@vue/shared";
const mutableHandlers = {
get(target, key, receiver) {
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
return Reflect.set(target, key, value, receiver);
},
};
export function reactive(target) {
if (!isObject(target)) {
return target;
}
const proxy = new Proxy(target, mutableHandlers);
return proxy;
}
如下圖,如果采用target[key]
方法,獲取aliasName
時(shí) 不會(huì)觸發(fā)name
的get
1.2 完善重復(fù)
const state = { name: "orange", age: 18 };
const p1 = reactive(state);
const p2 = reactive(state);
const p3 = reactive(p1);
console.log(p1 === p2);
console.log(p1 === p3);
- 響應(yīng)式數(shù)據(jù)緩存,防止重復(fù)代理
使用map
,key
為對(duì)象,值為響應(yīng)式對(duì)象,實(shí)現(xiàn)p1 === p2
const reactiveMap = new WeakMap(); //內(nèi)存泄漏
export function reactive(target) {
if (!isObject(target)) {
return target;
}
const exitProxy = reactiveMap.get(target);
if (exitProxy) {
return exitProxy;
}
const proxy = new Proxy(target, mutableHandlers);
reactiveMap.set(target, proxy);
return proxy;
}
- 響應(yīng)式數(shù)據(jù)標(biāo)記
用枚舉做標(biāo)記,響應(yīng)式對(duì)象都有g(shù)et和set方法,p1
初次創(chuàng)建時(shí)state
沒(méi)有ge
t和set
方法,target[ReactiveFlags.IS_REACTIVE]
取值為false
,創(chuàng)建p3
時(shí),p1
響應(yīng)式,取值為true
,直接返回p1
export const enum ReactiveFlags {
IS_REACTIVE = "__v_isReactive",
}
get(target, key, receiver) {
if (ReactiveFlags.IS_REACTIVE == key) {
return true;
}
return Reflect.get(target, key, receiver);
},
export function reactive(target) {
...,
if (target[ReactiveFlags.IS_REACTIVE]) {
return target;
}
const proxy = new Proxy(target, mutableHandlers);
reactiveMap.set(target, proxy);
return proxy;
}
2.實(shí)現(xiàn)effect
2.1 effect函數(shù)
vue2
與vue3
早期的依賴(lài)收集采用的都是棧方式存儲(chǔ),vue3
后來(lái)改為樹(shù)型數(shù)據(jù)存儲(chǔ)。effect
執(zhí)行時(shí),把當(dāng)前effect
作為全局的,觸發(fā)屬性的get
方法,收集依賴(lài)
let activeEffect;
class ReactiveEffect {
public deps: Array<any> = []; // 判斷依賴(lài)屬性
public active: boolean = true; // 是否激活
public parent = undefined;
constructor(public fn) {}
run() {
if (!this.active) {
return this.fn();
}
try {
this.parent = activeEffect;
activeEffect = this;
return this.fn();
} finally {
activeEffect = this.parent;
this.parent = undefined;
}
}
}
export function effect(fn) {
const _effect = new ReactiveEffect(fn);
_effect.run();
}
2.2 依賴(lài)收集
執(zhí)行effec
t時(shí),會(huì)觸發(fā)依賴(lài)屬性的get
方法,在屬性的get
中進(jìn)行依賴(lài)收集
get(target, key, receiver) {
if (ReactiveFlags.IS_REACTIVE == key) {
return true;
}
console.log(activeEffect, key);
track(target,key)
return Reflect.get(target, key, receiver);
},
/* 屬性變動(dòng)后 要重新執(zhí)行run 需要把屬性和effect關(guān)聯(lián)起來(lái) 收集對(duì)象上屬性關(guān)聯(lián)的effect
不能光記錄屬性,容易兩個(gè)對(duì)象有重名的屬性,所以需要帶著對(duì)象記錄
target為對(duì)象
let mapping = {
target:{
name:[effect1,effect2,effect3] 一個(gè)屬性可以在多個(gè)頁(yè)面使用
}
}
*/
const targetMap = new WeakMap();
export function track(target, key) {
// 如果取值操作沒(méi)有發(fā)生在effect中,則不需要收集依賴(lài)
if (!activeEffect) {
return;
}
// 判斷是否已經(jīng)記錄過(guò)
let depsMap = targetMap.get(target);
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()));
}
let dep = depsMap.get(key);
if (!dep) {
// 值為一個(gè)不重復(fù)數(shù)組
depsMap.set(key, (dep = new Set()));
}
let shouldTrack = !dep.has(key);
if (shouldTrack) {
dep.add(activeEffect);
activeEffect.deps.push(dep); //同時(shí)effect記住當(dāng)前這個(gè)屬性
}
}
2.3 屬性更改 觸發(fā)effect
set(target, key, value, receiver) {
// 數(shù)據(jù)變化后觸發(fā)對(duì)應(yīng)的effect方法
const oldValue = target[key];
let r = Reflect.set(target, key, value, receiver);
if (oldValue != value) {
trigger(target, key, value, oldValue);
}
return r;
},
export function trigger(target, key, newValue, oldValue) {
// 通過(guò)對(duì)象找到對(duì)應(yīng)的屬性 讓這個(gè)屬性對(duì)應(yīng)的effect重新執(zhí)行
const depsMap = targetMap.get(target);
if (!depsMap) {
return;
}
const dep = depsMap.get(key); // name 或者 age對(duì)應(yīng)的所有effect
const effects = [...dep];
// 運(yùn)行的是數(shù)組 刪除的是set
effects &&
effects.forEach((effect) => {
// 正在執(zhí)行的effect ,不要多次執(zhí)行
if (effect !== activeEffect) effect.run();
});
}
2.4 清除effect依賴(lài)
const state = reactive({ flag: true, name: "orange", age: 18 });
effect(() => {
// 副作用函數(shù) (effect執(zhí)行渲染了頁(yè)面)
console.log("render");
document.body.innerHTML = state.flag ? state.name : state.age;
});
setTimeout(() => {
state.flag = false;
setTimeout(() => {
console.log("修改name,原則上不更新");
state.name = "zf";
}, 1000);
}, 1000);
當(dāng)切換flag時(shí),effect的依賴(lài)已經(jīng)更新,但是修改name又觸發(fā)了依賴(lài),需要在每次收集依賴(lài)前先清空
function cleanupEffect(effect) {
let { deps } = effect; // 清理effect
for (let i = 0; i < deps.length; i++) {
deps[i].delete(effect);
}
effect.deps.length = 0;
}
為了避免無(wú)限循環(huán)頁(yè)面卡死情況,觸發(fā)依賴(lài)時(shí)拷貝出來(lái)一份屬性對(duì)應(yīng)的effec
t列表,因?yàn)榍宄臅r(shí)候在刪除effect
,邊刪除邊添加會(huì)造成死循環(huán)
2.5 effect失活
通過(guò)effect
返回當(dāng)前effect
實(shí)例,然后curEffect.effect.stop()
調(diào)用stop
方法
let runner = effect(() => {
// 副作用函數(shù) (effect執(zhí)行渲染了頁(yè)面)
console.log("render");
document.body.innerHTML = state.flag ? state.name : state.age;
});
runner.effect.stop()
stop() {
cleanupEffect(this);
this.active = false;
}
export function effect(fn) {
const _effect = new ReactiveEffect(fn);
_effect.run();
const runner = _effect.run.bind(_effect); //調(diào)用effect.effect.stop() 停止effect
runner.effect = _effect;
return runner;
}
失活后我們可以手動(dòng)調(diào)用 runner()
觸發(fā)effect
方法
2.6 調(diào)度執(zhí)行
trigger觸發(fā)時(shí),我們可以自己決定副作用函數(shù)執(zhí)行的時(shí)機(jī)、次數(shù)、及執(zhí)行方式文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-823067.html
export function effect(fn, options:any = {}) {
const _effect = new ReactiveEffect(fn,options.scheduler); // 創(chuàng)建響應(yīng)式effect
// if(options){
// Object.assign(_effect,options); // 擴(kuò)展屬性
// }
_effect.run(); // 讓響應(yīng)式effect默認(rèn)執(zhí)行
const runner = _effect.run.bind(_effect);
runner.effect = _effect;
return runner; // 返回runner
}
export function trigger(target, type, key?, newValue?, oldValue?) {
const depsMap = targetMap.get(target);
if (!depsMap) {
return
}
let effects = depsMap.get(key);
if (effects) {
effects = new Set(effects);
for (const effect of effects) {
if (effect !== activeEffect) {
if(effect.scheduler){ // 如果有調(diào)度函數(shù)則執(zhí)行調(diào)度函數(shù)
effect.scheduler()
}else{
effect.run();
}
}
}
}
}
2.7 深度代理
只有當(dāng)數(shù)據(jù)被獲取的時(shí)候才進(jìn)行代理,如果獲取到的數(shù)據(jù)還是對(duì)象,繼續(xù)代理文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-823067.html
get(target, key, receiver) {
if (ReactiveFlags.IS_REACTIVE == key) {
return true;
}
track(target, key);
let r = Reflect.get(target, key, receiver);
if (isObject(r)) {
return reactive(r);
}
return r;
},
到了這里,關(guān)于vue3源碼(二)reactive&effect的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!