Vue 3.6.0-rc.12 修正 watch 清理時機的回歸,Vapor 補上轉場邊角案例
Vue core GitHub Release · 2026-10-16
Vue 3.6 在 watch 的清理(cleanup)時機上多了一個回歸:getter 重新求值但值沒變時,onCleanup 會被提早執行。這個問題在 v3.6.0-rc.12 由 #15766 修正,讓行為回到 3.5 的語意。
rc.12 的 CHANGELOG 日期是 2026-10-16,Release 頁本身只指向該檔案,沒有另寫說明。
原本的問題
回報者在 #15765 指出,3.6.0-alpha.1 到 rc.10 都有此現象,3.5.43 則正常。條件很單純:watch 的來源是 getter,getter 重跑後回傳值與上次相同,所以 cb 不會被呼叫,但清理函式卻已被執行。
清理函式的用途是撤銷上一次 cb 留下的副作用,例如取消請求、移除事件監聽。照契約,它應該在「下一次 cb 執行前」或「watcher 停止時」才跑。值沒變、cb 沒跑,就代表上一次的副作用仍然有效,此時清掉會讓資源提前失效。
issue 的重現程式如下,兩個版本的輸出順序不同:
const a = ref(1)
watch(() => a.value > 0, (v, _, onCleanup) => {
onCleanup(() => log.push('onCleanup'))
}, { immediate: true })
a.value++ // getter 仍為 true,cb 不會重跑
scope.stop()| 版本 | a.value++ 之後 | scope.stop() 之後 |
|---|---|---|
| 3.5.43 | 無清理 | onCleanup、onWatcherCleanup |
| 3.6.0-rc.10 | onCleanup、onWatcherCleanup | 無 |
核心改動
PR 14cfe0f 的標題是「preserve watcher cleanups until callback reruns」。依 PR 摘要,當被監看的值沒變時,清理回呼會被保留,等到下一次 cb 執行前或 watcher 停止時才跑。
PR 摘要另外提到兩點:effect 本身的清理與 cb 的清理改為分開處理;混合註冊時,重跑或停止會依註冊順序執行,重複 stop 不會再跑一次。pre、post、sync 三種 flush 模式行為一致。摘要為自動產生,細節請以 diff 為準。
Vapor 的轉場與 KeepAlive
rc.12 的 Bug Fixes 大半是 runtime-vapor,集中在 Transition 與 KeepAlive 的組合:例如 #15895 讓 KeepAlive 分支的 in-out 離場在新元素進場之後才開始,#15866 讓 interop 片段等離場動畫結束才移除,#15867 改為從節點實際的 parentNode 移除 block。
這些修正指向同一件事:Vapor 與 vdom 混用(interop)時,動畫的先後與節點歸屬仍是主要不穩定來源。上一版 rc.11(2026-10-10)才新增「在 vdom Transition 內轉場 Vapor slot 內容」的功能(#15758),rc.12 緊接著補邊角案例,符合這個脈絡。
影響範圍
- 已在 3.6 prerelease 上用
watch搭配 getter 的專案:檢查onCleanup或onWatcherCleanup內有沒有取消請求、解除訂閱之類的動作。rc.10 及更早版本它們可能在值未變時被提早觸發,你的程式碼若已為此加了繞路,升到 rc.12 後可以拿掉。 - 在 vdom 應用中以 interop 嘗鮮 Vapor 元件的團隊:若遇過 KeepAlive 切換後離場動畫沒做完、節點殘留或 slot 在
out-in下閃爍,可對照上述 PR 編號重測。 - 用 Vapor 做 SSR 的專案:#15852 修正動態原生元素的子節點在 SSR 的輸出方式,hydration 不一致的問題可以重測。
- 想看 client bundle 體積的人:#15875 讓 hydration 查找用的 DOM 節點 helper 可被 tree-shake,數字 changelog 未提供。
要驗證自己是否中招,最快的做法是把上面的重現程式貼進專案,分別在 rc.10 與 rc.12 跑,比對清理出現的位置。Release notes 未說明 3.6 正式版的發布時間。
原始來源:Release v3.6.0-rc.12、minor 分支 CHANGELOG、Issue #15765、PR #15766