為什麼 響應式資料被重新賦值了三次,但是監聽回撥只執行了一次

kuai23333發表於2023-02-09
const { createApp } = Vue
const app = createApp({
    components: [],
    template: `
    <div>
        <button @click="handleClick">toggle</button>
    </div>
    `,
    data() {
        return {
            visible: false,
            test: 1,
        }
    },
    methods: {
        handleClick() {
            this.visible = true;
            this.visible = false;
            this.visible = true;
        },
    },
    watch: {
        visible(v) {
            console.log('visible change', v)
        },
    }
}).mount('#app')

上述程式碼點選toggle後的效果如下圖。visible change只log了一次。

原始碼

為什麼 響應式資料被多次修改,但是監聽回撥只執行了一次呢?

Vue 透過例項化 ReactiveEffect ,監聽getter的改變,回撥scheduler。透過這種方式去劫持 visible 的更改。

// packages\runtime-core\src\apiWatch.ts
let scheduler: EffectScheduler
if (flush === 'sync') {
    scheduler = job as any
} else if (flush === 'post') {
    scheduler = () => queuePostRenderEffect(job, instance && instance.suspense)
} else {
    job.pre = true
    if (instance) job.id = instance.uid
    scheduler = () => queueJob(job)
}

const effect = new ReactiveEffect(getter, scheduler) 
// getter在本場景中對應visible。scheduler會在getter更改時被呼叫

程式碼 1 執行後, visible 設定為 true 。Vue 監聽到 visible 的更改。執行 queueJob ,將負責執行回撥函式的 job 推入 queue 。並利用 Promise.then 的回撥函式會被放入微任務佇列的特性,使用 then 將執行 queue 中所有方法的任務放入微任務佇列中。

// packages\runtime-core\src\scheduler.ts
export function queueJob(job: SchedulerJob) {
  // 檢查queue是否為空、job是否存在queue中
  if (
    !queue.length ||
    !queue.includes(
      job,
      isFlushing && job.allowRecurse ? flushIndex + 1 : flushIndex
    )
  ) {
    if (job.id == null) {
      queue.push(job)
    } else {
      queue.splice(findInsertionIndex(job.id), 0, job)
    }
    queueFlush()
  }
}
function queueFlush() {
  if (!isFlushing && !isFlushPending) {
    isFlushPending = true
    currentFlushPromise = resolvedPromise.then(flushJobs) // flushJobs進入微任務
  }
}

程式碼 2 執行後, visible 設定為 false。Vue 知道 visible 更改後,發現 負責 visible 更新時回撥的 job 已經推入 queue,於是不再繼續執行推入操作。

程式碼 3 執行後,visible 設定為 true。後面同上,不再繼續執行推入操作。

handleClick 執行完畢。事件迴圈機制會去執行微任務佇列的任務。然後程式碼1執行後放入微任務佇列中的 job 得到了執行,回撥函式也得到了執行。

綜上所述,job因為只推入1次,所以只執行了一次,監聽回撥也只執行了一次。

 

相關文章