vuejs / core

🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
https://vuejs.org/
MIT License
47.85k stars 8.36k forks source link

Directives mounted called *after* beforeUpdate #12460

Open jods4 opened 1 week ago

jods4 commented 1 week ago

Vue version

3.5.13

Link to minimal reproduction

https://play.vuejs.org/#eNp9VE1vEzEQ/SuWL03VZFMUeglJBFQ5wAGqtogDRmizO9m48dorfyxBq/3vjL1foSS52TPP4zfznl3RD0URlQ7onC5MonlhiQHrCiJimS2tWTHJ80JpSx7iDMhWq5wwGk39zp9jtAdUxOxiIdTvR9iSuoO2GCYTJY0lha+yPEKOpBPimkm89ZnnoJwdja7JchWQURkL5/H+ujG5u71F5GLaEEVquLGQFyK2sCKESUIW9wrZSJCWzLlZMurLMEqmiF5MezCTdEytQU5bnkUvRkkcQOULMJpgBS5Afy0sR86MzknI+Fxg/TnErHYw7uLJDpL9ifiLOfgYow8aDOgSqfQ5G+sMbJNeP32BA677ZK5SJxB9IfkIRgnnOTawj06mSPsIF9h+CupwmT2b9cGCNF1TnqhH1gEflPLDO9f6QHcWvQ3nmKxxit91XBSgz5sIj2KlwUgV0f8Z5N3gEJQcBUfIiNGNyxhFyZtMopy0oNustwKTWycTz5Uo+eSvRO8ExlilN8+V2l8h17POCb5JeRkW/yxxw2XhkNQEZw4Ce8GiaCf7pwDcGIh1sgv2QksrJ1KyAeypqkIbde2tH8pMz9T/ZqDvyyrfGBplh6HWxShA1QPqOng8FNk4a7Ht94ngyR6ptJibGxw1l0m0mDaI7tIjAm3ECIWPpJ1beCAD7PVTud9xkZ6SGNONOJBzi7NOYcslrHFjRj/8gBDD6E/UygNQ0jZy/h2/UiPcfIla9xOdYNYarnXo8HkdWfbo/wo3DaC+ZQ+5xLUrX078RFGK2FrdOD7MualbTjZcpkO2H3h7/ERnv0rQ/rViZ7PoLnozo/VfG1jmXA==

Steps to reproduce

Just open the repro.

What is expected?

The result should show [ ok ] should be "ok".

What is actually happening?

The result shows [ bug ] should be "ok". Clicking on button "inc" will force a new render of the component, which will fix the page.

System Info

System:
    OS: Windows 10 10.0.19045
    CPU: (12) x64 Intel(R) Core(TM) i7-9850H CPU @ 2.60GHz
    Memory: 3.66 GB / 15.79 GB
  Binaries:
    Node: 21.7.0 - C:\Program Files\nodejs\node.EXE
    npm: 10.5.0 - C:\Program Files\nodejs\npm.CMD
  Browsers:
    Edge: Chromium (127.0.2651.98), ChromiumDev (129.0.2752.4)
  npmPackages:
    vue: ^3.5.13 => 3.5.13

Any additional comments?

This reproduction is extremely fragile and I had a hard time creating it. The core of the issue is incorrect queued hooks execution order, and most changes you would do to the repro are going to fix it. If you want to see the page working correctly from the get go, just remove the timeout in App.vue and directly set page.value = Page.

Here's what I found out when debugging this myself:

The input is driven by a v-model, so the value is not really "rendered" by script setup, but it's instead set on elements by vModelText core directive. This will explain why the interpolation {{ val }} is rendered with the correct value, whereas the <input> shows the initial value instead.

The reproduction is setup in such a way that vModelText hooks are called in incorrect order. After event onSetup is processed by Wrapper, the component is rendered again, and vModelText.beforeUpdate is called with the new value. If you break at this point you'll see the repro displaying the correct values.

But then, it looks like the directive mounted hook has not been executed yet. It is enqueued by Vue core mountElement function here: https://github.com/vuejs/core/blob/fc4bbf95c1f9c8d9591e82571008578da15d7677/packages/runtime-core/src/renderer.ts#L715-L725

The issue seems to be that this call has captured the initial directive value and so restores the previous, incorrect value into the input element.

edison1105 commented 1 day ago

Additionally, the onMounted of the Wrapper.vue is also called after onBeforeUpdate. see Playground, Observe the console logs.

linzhe141 commented 1 day ago

A simpler minimal reproduction

We can see that the breakpoints in setTimeout, beforeUpdate, and mounted are triggered in sequence