vuejs / core

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

Inconsistent behavior of template refs between development server and production builds #11373

Open Nicolas-Yazzoom opened 1 month ago

Nicolas-Yazzoom commented 1 month ago

Vue version

3.4.31

Link to minimal reproduction

https://stackblitz.com/edit/vitejs-vite-auh6nk?file=src%2FApp.vue,src%2Fcomponents%2FTestA.vue,src%2Fcomponents%2FTestB.vue&terminal=dev

Steps to reproduce

Observe behavior during dev:

Observe behavior after building:

What is expected?

In both cases the exposed method from TestB.vue should be called, showing the alert. In both cases listOfB should contain a reference to the TestB instance.

What is actually happening?

In the production build, the alert is not shown because listOfB does not contain a reference to the instance of TestB. Because of this, it cannot call the exposed method.

System Info

(Ran this in the StackBlitz terminal)


  System:
    OS: Linux 5.0 undefined
    CPU: (8) x64 Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz
    Memory: 0 Bytes / 0 Bytes
    Shell: 1.0 - /bin/jsh
  Binaries:
    Node: 18.20.3 - /usr/local/bin/node
    Yarn: 1.22.19 - /usr/local/bin/yarn
    npm: 10.2.3 - /usr/local/bin/npm
    pnpm: 8.15.6 - /usr/local/bin/pnpm
  npmPackages:
    vue: ^3.4.31 => 3.4.31 

Any additional comments?

The docs do not mention dynamically setting a template ref (using :ref=" " instead of ref=" ") is not allowed.

jh-leong commented 1 month ago

a minimal reproduction: Playground

as a workaround, use instance.refs (see the playground demo above) or function refs.

Nicolas-Yazzoom commented 1 month ago

I could not get your workaround to work at first. After some investigation I found a few more inconsistencies which might be related to this issue: Playground. Click the button in both DEV and PROD modes and compare the result in the console.

What I was doing first was init'ing the ref as an empty array according to the docs. Your playground init'ed them like this: ref(). I'm unsure what this does as the docs don't mention this.

Here are the results in a table:

Variable ref in <template> refers to variable ref in <script> init'ed as array ref.value in DEV instance.refs in DEV ref.value in PROD instance.refs in PROD
testA Yes Yes ✔️ Missing Empty array ✔️
testB Yes No ✔️ ✔️ undefined ✔️
testC No Yes ✔️ Missing ✔️ Missing
testD No No ✔️ ✔️ ✔️ Present as proxy. A and B are not a proxy and D was not a proxy during DEV.
yyx990803 commented 1 month ago

This is a duplicate of #4866

As said, this is mostly a design flaw and there's no real fix for it. I am thinking of providing a dedicate useTemplateRef function in 3.5.

Nicolas-Yazzoom commented 1 month ago

I was able to work around this issue like this. I updated my code to look like the testB case. I'm using getCurrentInstance() as suggested by @jh-leong which works both in DEV and PROD as long as you DON'T define const testB = ref([]). Defining the ref in <script setup> puts you in case A which breaks during DEV.