Subscribe on changes!

a component that passes slots to children forces children to rerender

avatar
Mar 13th 2022

Version

3.2.31

Reproduction link

github.com

Steps to reproduce

I've found some cases that shouldn't makes slot-component to update, but it does. Only case #1 works properly. It looks like a bug, because it affects at lot of cases when you want to pass slots through a component to a children with this technique.

Vue.component('W', {
  props: ['child'],
  template: `<component :is="child">
  <template v-for="(_, name) in $slots" v-slot:[name]="slotData"><slot :name="name" v-bind="slotData" /></template>
</component>`
})

What is expected?

no updates in children component

What is actually happening?

it does update

avatar
Mar 14th 2022

Test 2-4 are updating because you pass already-rendered vnodes to the child, instead of a stable slot function.

However, we generally can't give 100% guarantee that all slot functions only re-render in case one of their dependencies changes. The compiler can't always be sure that a slot only depends on its own content, especially when it comes to nested slots.

So it sometimes it has to de-optimize for consistency and have the parents update their children alongside.

All That being said, Test 5 seems unnecessary so it's worth a look, but I would not be suprised if its because of the nested slot.

avatar
Mar 16th 2022

Thank you for your explanation. I rewrote test 2 according to official documentation. Those 2 examples are equivalent (according to docs), but the case with a render function still makes the child component re-render. Bug?

<InnerChild> <template #test="scope"> {{ scope.foo }} </template> </InnerChild>

() => <InnerChild>{{ foo: (scope) => scope.foo }}</InnerChild>.

And it would be nice if we could just pass slots to children this way without loosing an optimization.

setup(props, { slots }) { return () => h( InnerChild, { }, slots, ) }

Because now the best solution of this problem to avoid using slots API and pass render functions with props.

avatar
Mar 16th 2022

I must have been sleep-deprived when I wrote my initial reply as none of the the examples in the repo are actually passing pre-rendered vnodes. Sorry.

Will need to take a closer look, but likely, Evan will have to dig into this as this part of the codebase is pretty complex in terms of interactions that are happening.

But my general point still stands - we sometimes can't ensure that a slot is actually stable and so parents have to trigger re-renders in children. This is more often the case when using pure render functions as those don't contain the compiler-generated optimization hints.

Maybe there's room for improvement in providing helpers to add those manually.

avatar
Aug 15th 2022

I met same problem. I created repository for reproducing.

Demo shows input with available clickable options. If you click on one of element, you'll see that update hook will called on each element. (It shows on page Children updated and in console).

If remove slot tag in components/MyDropdownList.vue, children wont's update. On big lists and nested templates with component-like this in my project I have very low performance. Maybe someone has workaround?

avatar
Oct 7th 2022

@bonilka to workaround this problem you always can avoid using slot api, just pass render function that would return VNode and render this VNode in a component template

https://stackoverflow.com/questions/49352525/can-you-render-vnodes-in-a-vue-template