vuejs / core

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

TransitionGroup - item keys change when using v-if and this messes with the transition #6180

Open ddenev opened 2 years ago

ddenev commented 2 years ago

Vue version

3.2.37

Link to minimal reproduction

Reproduction link

Steps to reproduce

I am not 100% sure this is a bug but this code was properly working in Vue2.

Problem is that the keys of items in transition-group get changed when there is a v-if statement. This is a big problem b/c now, when I insert an item into the list, ALL items get animated, not just the inserted one. Here is how to reproduce:

  1. Go to the provided link and open the Console
  2. Click on the "Toggle & Add" button
  3. Check the Console - you will see that with every toggle the item keys are changed a. It is also strange why the keys are not exactly as I provided them but are prefixed with "0_item" or "1_item" - maybe this is part of the problem b. you can also observe they keys in Vue Devtools
  4. Observe the animation - all items are animated, not only the inserted one

Why I need this: I'm working on a drag-n-drop set of components and my DropList component needs to take care of inserting new items as well as reordering of new items. These 2 cases are managed by a v-if statement in the default slot of transition-group, something like (simplified):

<transition-group ref="transitionGroupRef">
  <template v-if="dropAllowed && dropEntered">
    <template v-if="isReordering">
      <slot v-for="(item, index) of reorderedItems" name="item" :item="item" />
    </template>
    <template v-else>
      <slot v-for="item of itemsBeforePlaceholder" name="item" :item="item" />
      <slot name="placeholder" />
      <slot v-for="item of itemsAfterPlaceholder" name="item" :item="item" />
    </template>
  </template>
  <template v-else>
    <slot v-for="item of items" name="item" :item="item" />
  </template>
</transition-group>

What is expected?

I would expect that the keys of the items do not change just because of the v-if statement as I am providing a list with the same keys

What is actually happening?

Vue is overriding my keys with its own an this messes with the list transition.

System Info

No response

Any additional comments?

No response

DanSweeney23 commented 2 years ago

I don't think you can really expect it to keep the same keys. You are deleting the vnodes with that v-if and creating new ones. Imagine every time you added something like <div v-for="(item, index) of items" :key="index"> in an app, vnodes would identical keys.

ddenev commented 2 years ago

Thanks for the reply @DanSweeney23 ! I do not agree though ;)

I don't think you can really expect it to keep the same keys. You are deleting the vnodes with that v-if and creating new ones

On the contrary, I would expect exactly "to keep the same keys", exactly because I am providing keys for those vnodes and that's one of the main ideas of keys - to have a unique key for vnode identification. Therefore if I'm providing the same key that should be an indication that this should be the same vnode as far as lists go. Keep in mind that worked just fine in Vue2. Vue3 (with introduction of fragments) changes that and I don't know if this is a bug or by design. If it's by design, it seems very strange to me why, when I provide the keys for the vnodes, Vue3 decides to change them and therefore override my implementation of key uniqueness.

Imagine every time you added something like <div v-for="(item, index) of items" :key="index"> in an app, vnodes would identical keys.

Unfortunately I fail to understand what you mean by that statement

edison1105 commented 2 months ago

This is because the key inherits the key generated by v-if and v-else. https://github.com/vuejs/core/blob/df0edd8168e930ccdda000fa9b1c36d4dfaa017e/packages/runtime-core/src/components/BaseTransition.ts#L536-L540