ホームページ > 記事 > ウェブフロントエンド > vue3 で応答原理エフェクトを実装する方法
export let activeEffect = undefined;// 当前正在执行的effect class ReactiveEffect { active = true; deps = []; // 收集effect中使用到的属性 parent = undefined; constructor(public fn) { } run() { if (!this.active) { // 不是激活状态 return this.fn(); } try { this.parent = activeEffect; // 当前的effect就是他的父亲 activeEffect = this; // 设置成正在激活的是当前effect return this.fn(); } finally { activeEffect = this.parent; // 执行完毕后还原activeEffect this.parent = undefined; } } } export function effect(fn, options?) { const _effect = new ReactiveEffect(fn); // 创建响应式effect _effect.run(); // 让响应式effect默认执行 }
get(target, key, receiver) { if (key === ReactiveFlags.IS_REACTIVE) { return true; } const res = Reflect.get(target, key, receiver); track(target, 'get', key); // 依赖收集 return res; }
const targetMap = new WeakMap(); // 记录依赖关系 export function track(target, type, key) { if (activeEffect) { let depsMap = targetMap.get(target); // {对象:map} if (!depsMap) { targetMap.set(target, (depsMap = new Map())) } let dep = depsMap.get(key); if (!dep) { depsMap.set(key, (dep = new Set())) // {对象:{ 属性 :[ dep, dep ]}} } let shouldTrack = !dep.has(activeEffect) if (shouldTrack) { dep.add(activeEffect); activeEffect.deps.push(dep); // 让effect记住dep,这样后续可以用于清理 } } }
属性と対応するエフェクトの間のマッピング関係を維持します。後続の属性変更により、対応するエフェクト関数の再実行がトリガーされる可能性があります
#更新のトリガーset(target, key, value, receiver) { // 等会赋值的时候可以重新触发effect执行 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); // 获取对应的映射表 if (!depsMap) { return } const effects = depsMap.get(key); effects && effects.forEach(effect => { if (effect !== activeEffect) effect.run(); // 防止循环 }) }
const state = reactive({ flag: true, name: 'jw', age: 30 }) effect(() => { // 副作用函数 (effect执行渲染了页面) 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; // 当前的effect就是他的父亲 activeEffect = this; // 设置成正在激活的是当前effect + cleanupEffect(this); return this.fn(); // 先清理在运行 } } }ここで注意すべき点は、クリーンアップ操作は次のとおりです。トリガー時に実行され(クリーニング効果)、その後再び収集されます(収集効果)。ループ中に無限ループが発生します。
let effect = () => {}; let s = new Set([effect]) s.forEach(item=>{s.delete(effect); s.add(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 }
export function effect(fn, options:any = {}) { const _effect = new ReactiveEffect(fn,options.scheduler); // 创建响应式effect // if(options){ // Object.assign(_effect,options); // 扩展属性 // } _effect.run(); // 让响应式effect默认执行 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){ // 如果有调度函数则执行调度函数 effect.scheduler() }else{ effect.run(); } } } } }ディーププロキシ
get(target, key, receiver) { if (key === ReactiveFlags.IS_REACTIVE) { return true; } // 等会谁来取值就做依赖收集 const res = Reflect.get(target, key, receiver); track(target, 'get', key); if(isObject(res)){ return reactive(res); } return res; }
以上がvue3 で応答原理エフェクトを実装する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。