vue3中的響應(yīng)式原理-effect
effect的基本實(shí)現(xiàn)
export let activeEffect = undefined;// 當(dāng)前正在執(zhí)行的effect class ReactiveEffect { ? ? active = true; ? ? deps = []; // 收集effect中使用到的屬性 ? ? parent = undefined; ? ? constructor(public fn) { } ? ? run() { ? ? ? ? if (!this.active) { // 不是激活狀態(tài) ? ? ? ? ? ? return this.fn(); ? ? ? ? } ? ? ? ? try { ? ? ? ? ? ? this.parent = activeEffect; // 當(dāng)前的effect就是他的父親 ? ? ? ? ? ? activeEffect = this; // 設(shè)置成正在激活的是當(dāng)前effect ? ? ? ? ? ? return this.fn(); ? ? ? ? } finally { ? ? ? ? ? ? activeEffect = this.parent; // 執(zhí)行完畢后還原activeEffect ? ? ? ? ? ? this.parent = undefined; ? ? ? ? } ? ? } } export function effect(fn, options?) { ? ? const _effect = new ReactiveEffect(fn); // 創(chuàng)建響應(yīng)式effect ? ? _effect.run(); // 讓響應(yīng)式effect默認(rèn)執(zhí)行 }
依賴(lài)收集
get(target, key, receiver) { ? ? if (key === ReactiveFlags.IS_REACTIVE) { ? ? ? ? return true; ? ? } ? ? const res = Reflect.get(target, key, receiver); ? ? track(target, 'get', key); ?// 依賴(lài)收集 ? ? return res; }
const targetMap = new WeakMap(); // 記錄依賴(lài)關(guān)系 export function track(target, type, key) { ? ? if (activeEffect) { ? ? ? ? let depsMap = targetMap.get(target); // {對(duì)象:map} ? ? ? ? if (!depsMap) { ? ? ? ? ? ? targetMap.set(target, (depsMap = new Map())) ? ? ? ? } ? ? ? ? let dep = depsMap.get(key); ? ? ? ? if (!dep) { ? ? ? ? ? ? depsMap.set(key, (dep = new Set())) // {對(duì)象:{ 屬性 :[ dep, dep ]}} ? ? ? ? } ? ? ? ? let shouldTrack = !dep.has(activeEffect) ? ? ? ? if (shouldTrack) { ? ? ? ? ? ? dep.add(activeEffect); ? ? ? ? ? ? activeEffect.deps.push(dep); // 讓effect記住dep,這樣后續(xù)可以用于清理 ? ? ? ? } ? ? } }
將屬性和對(duì)應(yīng)的effect維護(hù)成映射關(guān)系,后續(xù)屬性變化可以觸發(fā)對(duì)應(yīng)的effect函數(shù)重新run
觸發(fā)更新
set(target, key, value, receiver) { ? ? // 等會(huì)賦值的時(shí)候可以重新觸發(fā)effect執(zhí)行 ? ? let oldValue = target[key] ? ? const result = Reflect.set(target, key, value, receiver); ? ? if (oldValue !== value) { ? ? ? ? trigger(target, 'set', key, value, oldValue) ? ? } ? ? return result; }
export function trigger(target, type, key?, newValue?, oldValue?) { ? ? const depsMap = targetMap.get(target); // 獲取對(duì)應(yīng)的映射表 ? ? if (!depsMap) { ? ? ? ? return ? ? } ? ? const effects = depsMap.get(key); ? ? effects && effects.forEach(effect => { ? ? ? ? if (effect !== activeEffect) effect.run(); // 防止循環(huán) ? ? }) }
分支切換與cleanup
在渲染時(shí)我們要避免副作用函數(shù)產(chǎn)生的遺留
const state = reactive({ flag: true, name: 'jw', age: 30 }) 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)
function cleanupEffect(effect) { ? ? const { deps } = effect; // 清理effect ? ? for (let i = 0; i < deps.length; i++) { ? ? ? ? deps[i].delete(effect); ? ? } ? ? effect.deps.length = 0; } class ReactiveEffect { ? ? active = true; ? ? deps = []; // 收集effect中使用到的屬性 ? ? parent = undefined; ? ? constructor(public fn) { } ? ? run() { ? ? ? ? try { ? ? ? ? ? ? this.parent = activeEffect; // 當(dāng)前的effect就是他的父親 ? ? ? ? ? ? activeEffect = this; // 設(shè)置成正在激活的是當(dāng)前effect + ? ? ? ? ? cleanupEffect(this); ? ? ? ? ? ? return this.fn(); // 先清理在運(yùn)行 ? ? ? ? } ? ? } }
這里要注意的是:觸發(fā)時(shí)會(huì)進(jìn)行清理操作(清理effect),在重新進(jìn)行收集(收集effect)。在循環(huán)過(guò)程中會(huì)導(dǎo)致死循環(huán)。
let effect = () => {}; let s = new Set([effect]) s.forEach(item=>{s.delete(effect); s.add(effect)}); // 這樣就導(dǎo)致死循環(huán)了
停止effect
export class ReactiveEffect { ? ? stop(){ ? ? ? ? if(this.active){? ? ? ? ? ? ? cleanupEffect(this); ? ? ? ? ? ? this.active = false ? ? ? ? } ? ? } } export function effect(fn, options?) { ? ? const _effect = new ReactiveEffect(fn);? ? ? _effect.run(); ? ? const runner = _effect.run.bind(_effect); ? ? runner.effect = _effect; ? ? return runner; // 返回runner }
調(diào)度執(zhí)行
trigger觸發(fā)時(shí),我們可以自己決定副作用函數(shù)執(zhí)行的時(shí)機(jī)、次數(shù)、及執(zhí)行方式
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();? ? ? ? ? ? ? ? ? } ? ? ? ? ? ? } ? ? ? ? } ? ? } }
深度代理
get(target, key, receiver) { ? ? if (key === ReactiveFlags.IS_REACTIVE) { ? ? ? ? return true; ? ? } ? ? // 等會(huì)誰(shuí)來(lái)取值就做依賴(lài)收集 ? ? const res = Reflect.get(target, key, receiver); ? ? track(target, 'get', key); ? ? if(isObject(res)){ ? ? ? ? return reactive(res); ? ? } ? ? return res; }
當(dāng)取值時(shí)返回的值是對(duì)象,則返回這個(gè)對(duì)象的代理對(duì)象,從而實(shí)現(xiàn)深度代理
總結(jié)
為了實(shí)現(xiàn)響應(yīng)式,我們使用了new Proxy
effect默認(rèn)數(shù)據(jù)變化要能更新,我們先將正在執(zhí)行的effect作為全局變量,渲染(取值),然后在get方法中進(jìn)行依賴(lài)收集
依賴(lài)收集的數(shù)據(jù)格式weakMap(對(duì)象:map(屬性:set(effect))
用戶(hù)數(shù)據(jù)發(fā)生變化,會(huì)通過(guò)對(duì)象屬性來(lái)查找對(duì)應(yīng)的effect集合,全部執(zhí)行;
調(diào)度器的實(shí)現(xiàn),創(chuàng)建effect時(shí),把scheduler存在實(shí)例上,調(diào)用runner時(shí),判斷如果有調(diào)度器就調(diào)用調(diào)度器,否則執(zhí)行runner
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
vue+element下日期組件momentjs轉(zhuǎn)換賦值問(wèn)題解決
這篇文章主要介紹了vue+element下日期組件momentjs轉(zhuǎn)換賦值問(wèn)題,記錄下使用momentjs轉(zhuǎn)換日期字符串賦值給element的日期組件報(bào)錯(cuò)問(wèn)題,需要的朋友可以參考下2024-02-02Vue向后端傳數(shù)據(jù)后端接收為null問(wèn)題及解決
這篇文章主要介紹了Vue向后端傳數(shù)據(jù)后端接收為null問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-09-09Vue判斷數(shù)組內(nèi)是否存在某一項(xiàng)的兩種方法
這篇文章主要介紹了Vue判斷數(shù)組內(nèi)是否存在某一項(xiàng),今天給大家分享兩種方法,分別是findIndex()和 indexOf()方法,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2023-07-07淺談Vue static 靜態(tài)資源路徑 和 style問(wèn)題
這篇文章主要介紹了淺談Vue static 靜態(tài)資源路徑 和 style問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-11-11