Browse Source

proper slot duplication warning (fix #3595)

Evan You 9 years ago
parent
commit
4afccc8eec

+ 2 - 0
flow/component.js

@@ -93,6 +93,8 @@ declare interface Component {
   _f: (id: string) => Function;
   // renderList
   _l: (val: any, render: Function) => ?Array<VNode>;
+  // renderSlot
+  _t: (name: string, fallback: ?Array<VNode>) => ?Array<VNode>;
   // apply v-bind object
   _b: (vnode: VNodeWithData, value: any) => void;
   // retrive custom keyCode

+ 3 - 3
src/compiler/codegen/index.js

@@ -221,11 +221,11 @@ function genText (text: ASTText | ASTExpression): string {
 }
 
 function genSlot (el: ASTElement): string {
-  const slot = `$slots[${el.slotName || '"default"'}]`
+  const slotName = el.slotName || '"default"'
   const children = genChildren(el)
   return children
-    ? `(${slot}||${children})`
-    : slot
+    ? `_t(${slotName},${children})`
+    : `_t(${slotName})`
 }
 
 function genComponent (el: any): string {

+ 0 - 20
src/compiler/parser/index.js

@@ -34,7 +34,6 @@ let preTransforms
 let transforms
 let postTransforms
 let delimiters
-let seenSlots: any
 
 /**
  * Convert HTML string to AST.
@@ -51,7 +50,6 @@ export function parse (
   transforms = pluckModuleFunction(options.modules, 'transformNode')
   postTransforms = pluckModuleFunction(options.modules, 'postTransformNode')
   delimiters = options.delimiters
-  seenSlots = Object.create(null)
   const stack = []
   const preserveWhitespace = options.preserveWhitespace !== false
   let root
@@ -326,25 +324,7 @@ function processOnce (el) {
 
 function processSlot (el) {
   if (el.tag === 'slot') {
-    if (process.env.NODE_ENV !== 'production') {
-      if (!el.attrsMap[':name'] && !el.attrsMap['v-bind:name'] && checkInFor(el)) {
-        warn(
-          'Static <slot> found inside v-for: they will not render correctly. ' +
-          'Render the list in parent scope and use a single <slot> instead.'
-        )
-      }
-    }
     el.slotName = getBindingAttr(el, 'name')
-    if (process.env.NODE_ENV !== 'production') {
-      const name = el.slotName
-      if (seenSlots[name]) {
-        warn(
-          `Duplicate ${name ? `<slot> with name ${name}` : `default <slot>`} ` +
-          `found in the same template.`
-        )
-      }
-      seenSlots[name] = true
-    }
   } else {
     const slotTarget = getBindingAttr(el, 'slot')
     if (slotTarget) {

+ 24 - 7
src/core/instance/render.js

@@ -36,13 +36,6 @@ export function renderMixin (Vue: Class<Component>) {
       _parentVnode
     } = vm.$options
 
-    if (vm._isMounted) {
-      // clone slot nodes on re-renders
-      for (const key in vm.$slots) {
-        vm.$slots[key] = cloneVNodes(vm.$slots[key])
-      }
-    }
-
     if (staticRenderFns && !vm._staticTrees) {
       vm._staticTrees = []
     }
@@ -155,6 +148,30 @@ export function renderMixin (Vue: Class<Component>) {
     return ret
   }
 
+  // renderSlot
+  Vue.prototype._t = function (
+    name: string,
+    fallback: ?Array<VNode>
+  ): ?Array<VNode> {
+    let slotNodes = this.$slots[name]
+    if (slotNodes) {
+      // warn duplicate slot usage
+      if (process.env.NODE_ENV !== 'production') {
+        slotNodes._rendered && warn(
+          `Duplicate presense of slot "${name}" found in the same render tree ` +
+          `- this will likely cause render errors.`,
+          this
+        )
+        slotNodes._rendered = true
+      }
+      // clone slot nodes on re-renders
+      if (this._isMounted) {
+        slotNodes = cloneVNodes(slotNodes)
+      }
+    }
+    return slotNodes || fallback
+  }
+
   // apply v-bind object
   Vue.prototype._b = function bindProps (
     vnode: VNodeWithData,

+ 29 - 16
test/unit/features/component/component-slot.spec.js

@@ -458,28 +458,41 @@ describe('Component slot', () => {
   it('warn duplicate slots', () => {
     new Vue({
       template: `<div>
-        <slot></slot><slot></slot>
-        <slot name="a"></slot><slot name="a"></slot>
-      </div>`
+        <test>
+          <div>foo</div>
+          <div slot="a">bar</div>
+        </test>
+      </div>`,
+      components: {
+        test: {
+          template: `<div>
+            <slot></slot><slot></slot>
+            <div v-for="i in 3"><slot name="a"></slot></div>
+          </div>`
+        }
+      }
     }).$mount()
-    expect('Duplicate default <slot>').toHaveBeenWarned()
-    expect('Duplicate <slot> with name "a"').toHaveBeenWarned()
+    expect('Duplicate presense of slot "default"').toHaveBeenWarned()
+    expect('Duplicate presense of slot "a"').toHaveBeenWarned()
   })
 
-  it('warn static slot inside v-for', () => {
-    new Vue({
-      template: `<div>
-        <div v-for="i in 1"><slot :name="'test' + i"></slot></div>
-      </div>`
-    }).$mount()
-    expect('Static <slot> found inside v-for').not.toHaveBeenWarned()
-
+  it('should not warn valid conditional slots', () => {
     new Vue({
       template: `<div>
-        <div v-for="i in 1"><slot></slot></div>
-      </div>`
+        <test>
+          <div>foo</div>
+        </test>
+      </div>`,
+      components: {
+        test: {
+          template: `<div>
+            <slot v-if="true"></slot>
+            <slot v-else></slot>
+          </div>`
+        }
+      }
     }).$mount()
-    expect('Static <slot> found inside v-for').toHaveBeenWarned()
+    expect('Duplicate presense of slot "default"').not.toHaveBeenWarned()
   })
 
   // #3518

+ 3 - 3
test/unit/modules/compiler/codegen.spec.js

@@ -98,21 +98,21 @@ describe('codegen', () => {
   it('generate single slot', () => {
     assertCodegen(
       '<slot></slot>',
-      `with(this){return $slots["default"]}`
+      `with(this){return _t("default")}`
     )
   })
 
   it('generate named slot', () => {
     assertCodegen(
       '<slot name="one"></slot>',
-      `with(this){return $slots["one"]}`
+      `with(this){return _t("one")}`
     )
   })
 
   it('generate slot fallback content', () => {
     assertCodegen(
       '<slot><div>hi</div></slot>',
-      `with(this){return ($slots["default"]||[_m(0)])}`,
+      `with(this){return _t("default",[_m(0)])}`,
       [`with(this){return _h('div',["hi"])}`]
     )
   })