vuejs / composition-api

Composition API plugin for Vue 2
https://composition-api.vuejs.org/
MIT License
4.19k stars 343 forks source link

`context.slots` is not mirrored from `$slots` or `$scopedSlots` currectly after setup. Is it needed to run `resolveScopedSlots` again after each updated lifecycle to sync the `context.slots`? #942

Closed skytt closed 2 years ago

skytt commented 2 years ago

I use a wrapper components, which contains a <slots /> element to allow another component rendered inside. Once the slots element was shown after the setup process, I found the content of context.slots of wrapper component doesn't mirrored from $scopedSlots or $slots.

I found another issue about this problem, #911. Here is a test case to explain the situation:

it('should synchronously update context.slots after each render', async () => {
  let context: SetupContext = undefined!

  const Wrapper = defineComponent({
    setup(_, ctx) {
      context = ctx
    },
    template: `<div><slot /></div>`,
  })

  const Foo = defineComponent({
    components: {
      Wrapper,
    },
    setup() {
      const showDiv = ref(false)
      return {
        showDiv,
      }
    },
    template: `<wrapper><div v-if="showDiv"></div></wrapper>`,
  })

  const vm = createApp(Foo).mount()

  await nextTick()
  //@ts-ignore
  vm.showDiv = true
  await nextTick()
  //@ts-ignore
  expect(context.slots.default).toBeDefined()
})

It runs failed due to the undefined of the slots.default.

skytt commented 2 years ago

I try to figure out this problem, and found that adding resolveScopedSlots func intoafterRender (src/utils/instance.ts) can make the context.slots synchronized context.slots and actual slot content, like:

export function afterRender(vm: ComponentInstance) {
+  const attrs = vmStateManager.get(vm, 'attrBindings')
+  if (attrs?.ctx.slots) {
+    resolveScopedSlots(vm, attrs.ctx.slots)
+  }
  const stack = [(vm as any)._vnode as VNode]
  while (stack.length) {
    const vnode = stack.pop()
    if (vnode) {
      if (vnode.context) updateTemplateRef(vnode.context)
      if (vnode.children) {
        for (let i = 0; i < vnode.children.length; ++i) {
          stack.push(vnode.children[i])
        }
      }
    }
  }
}

But I don't think this is the best practice to solve the problem because the method to get the setupContext is a bit of a hook.

Is it any ideas to solve it?

github-actions[bot] commented 2 years ago

Stale issue message

kajweb commented 9 months ago

Solve it?