vuejs / vue

This is the repo for Vue 2. For Vue 3, go to https://github.com/vuejs/core
http://v2.vuejs.org
MIT License
207.45k stars 33.64k forks source link

Tunneling slots inside a scoped-slot triggers warning incorrectly #8587

Closed shentao closed 5 years ago

shentao commented 5 years ago

Version

2.5.16

Reproduction link

https://codesandbox.io/s/rllvz0m21o

Steps to reproduce

  1. Create a "renderless" component that passes data into the default scoped-slot.
    render () {
    return this.$scopedSlots.default(this)
    },
  2. Have a component that uses the "renderless" component as root and then populates the scoped-slot with its own template.
  3. Have two components with regular slots where one is tunneling the passed content into the second slot. The slots can have different names, it does not affect the issue. Like this:
    <slot slot="slotName" name="slotName"/>
  4. Somehow force the "renderless" component to update.

What is expected?

No warning should be shown.

What is actually happening?

It triggers the warning:

Duplicate presence of slot "sep" found in the same render tree - this will likely cause render errors.

found in

---> <MiddleComponent> at /src/components/MiddleComponent.vue

It might also work incorrectly or trigger additional unnecessary re-renders.


The source code responsible for showing the warning was probably meant for detecting the usage of slots inside v-for loops. However, in this case, it seems to be called incorrectly since the slot is only rendered once. The reason for this might be that the rendered flag is not being reset in this situation. Not using a renderless component (doing tunneling outside of a scoped-slot) does not trigger the error. Using scoped-slots instead of regular slots also does not trigger the warning, but that’s because the check is skipped.

This is potentially related to #8546.

Justineo commented 5 years ago

Looks like a duplicate of #8546?

HcySunYang commented 5 years ago

This problem occurs when a scope slot has a <slot> child node. I thought of a way.

Add a new render helper.

  1. Add the resetRenderedflag function to the src/core/instance/render-helpers/render-slot.js file:
    export function resetRenderedflag () {
    // Copy from Vue.prototype._render
    if (process.env.NODE_ENV !== 'production') {
    for (const key in this.$slots) {
      // $flow-disable-line
      this.$slots[key]._rendered = false
    }
    }
    }
  2. Then at src/core/instance/render-helpers/index.js:
    
    import { renderSlot, resetRenderedflag } from './render-slot'

export function installRenderHelpers (target: any) { // ... target._r = resetRenderedflag // Newly added }

3. Finally, modify the src/compiler/codegen/index.js file:
```js {2}
function genScopedSlot (
  key: string,
  el: ASTElement,
  state: CodegenState
): string {
  if (el.for && !el.forProcessed) {
    return genForScopedSlot(key, el, state)
  }
  const fn = `function(${String(el.slotScope)}){` +
    `_r();` + // Pay attention to here
    `return ${el.tag === 'template'
      ? el.if
        ? `${el.if}?${genChildren(el, state) || 'undefined'}:undefined`
        : genChildren(el, state) || 'undefined'
      : genElement(el, state)
    }}`
  return `{key:${key},fn:${fn}}`
}

I originally wanted to submit a PR, Is there any better way?.🤡

yyx990803 commented 5 years ago

Should be fixed by 530ca1b2