vue 响应式追踪与调度区别

63 阅读2分钟
  • pauseTracking/resetTracking:读但不订阅(依赖收集开关,支持嵌套)。
  • pauseScheduling/resetScheduling:先做完一批,再统一调度(调度合并)。
    // 极简依赖收集 + 响应式示例
    
    pauseTracking/resetTracking(读但不订阅)

    说明:模拟一个极简依赖收集系统;对比“未暂停 vs 暂停 tracking”时,hook 内读取是否会被收集为依赖。

    let sholudTrack = true
    trackStack = []
    let activeEffect = null 
    const dep = new Set()
    
    function pauseTracking(){
        trackStack.push(sholudTrack)
        sholudTrack = false
    }
    
    function resetTracking(){
        const last = trackStack.pop()
        shouldTrack = last === undefined ? true : last
    }
    
    funtion track(){
        if(shouldTrack && activeEffect) dep.add(activeEffect())
    }
    
    function trigger(){
          dep.forEach((e) => e());
    }
    
    const state = {
        _count: 0,
        get count(){
            track()
            return this._count
        }
        set count(v){
           trigger()
           this._count = v
        }
    }
    
    function effect(fn){
       const runner = ()=>{
           activeEffect = runner;
           fn();
           activeEffect = null;
       }
        runner()
        return runner
    }
    
    
   // 情况 A:未暂停 tracking(会被误收集)
    effect(() => {
      console.log('effect A start');
      hookReadsCount('hook A'); // 这次读取会被收集进 A
      console.log('effect A end');
    });
    console.log('--- 修改 count,将触发 A(含 hook 的那次读取) ---');
    state.count++;

    // 情况 B:暂停 tracking(不会被收集)
    effect(() => {
      console.log('effect B start');
      pauseTracking();
      hookReadsCount('hook B'); // 不会被收集进 B
      resetTracking();
      console.log('effect B end');
    });
    console.log('--- 再次修改 count,仅触发 B 中真正订阅的读取 ---');
    state.count++;
    
    要点:
    -   未暂停时,hook 内读取被“当前活跃 effect”订阅(可能是误收集)。
    -   暂停后,同样读取不再被收集,避免误订阅、依赖污染、潜在循环。
    
    
   pauseScheduling/resetScheduling(调度合并)
   模拟调度器队列;对比“暂停期间多次入队仅在恢复时统一 flush 一次”,体现批处理。
   
   let pauseScheduleStack = 0
   const queue = []
   
   function pauseScheduling = ()=>{
       pauseScheduleStack++
   }
   
   function resetSchduling = ()=>{
       pauseScheduleStack--
       if(!pauseScheduleStack)&&flush();
   }
   
   function schedule = (job)=>{
       queue.push(job)
       if(!pauseScheduleStack)&&flush();
   }
   
   function flush(){
       if(!queue.length) return 
       const jobs = queue.splice(0);
       console.log('flush once, jobs =', jobs.length);
       for (const j of jobs) j();
   }
   
   // 模拟:多次触发同一 effect 的调度函数
    const job = () => console.log('run effect scheduler');

    console.log('--- 未暂停:每次 schedule 立即 flush ---');
    schedule(job); // 立刻 flush
    schedule(job); // 立刻 flush

    console.log('--- 暂停调度:批量入队,恢复时统一 flush 一次 ---');
    pauseScheduling();
    schedule(job);
    schedule(job);
    schedule(job);
    console.log('(此时未 flush)');
    resetScheduling(); // 恢复时一次性 flush 3 个
    
    -   暂停期间,多次 schedule 仅入队不执行;恢复时批量 flush,一次完成,减少抖动。
    -   在 Vue 内部,数组/Map/Set 的“写方法”常用 pauseTracking + pauseScheduling 组合:既避免写过程中内部“读”被误收集,也把多次变动合并为一次调度。