Browse Source

support functional components as named slot (fix #3872)

Evan You 9 years ago
parent
commit
3446d1485d

+ 1 - 1
src/core/instance/render.js

@@ -232,7 +232,7 @@ export function resolveSlots (
     child = children[i]
     // named slots should only be respected if the vnode was rendered in the
     // same context.
-    if (child.context === context &&
+    if ((child.context === context || child.functionalContext === context) &&
         child.data && (name = child.data.slot)) {
       const slot = (slots[name] || (slots[name] = []))
       if (child.tag === 'template') {

+ 6 - 1
src/core/vdom/create-component.js

@@ -100,7 +100,7 @@ function createFunctionalComponent (
       props[key] = validateProp(key, propOptions, propsData)
     }
   }
-  return Ctor.options.render.call(
+  const vnode = Ctor.options.render.call(
     null,
     // ensure the createElement function in functional components
     // gets a unique context - this is necessary for correct named slot check
@@ -113,6 +113,11 @@ function createFunctionalComponent (
       slots: () => resolveSlots(children, context)
     }
   )
+  vnode.functionalContext = context
+  if (data.slot) {
+    (vnode.data || (vnode.data = {})).slot = data.slot
+  }
+  return vnode
 }
 
 export function createComponentInstanceForVnode (

+ 2 - 0
src/core/vdom/vnode.js

@@ -8,6 +8,7 @@ export default class VNode {
   elm: Node | void;
   ns: string | void;
   context: Component | void; // rendered in this component's scope
+  functionalContext: Component | void; // only for functional component root nodes
   key: string | number | void;
   componentOptions: VNodeComponentOptions | void;
   child: Component | void; // component instance
@@ -35,6 +36,7 @@ export default class VNode {
     this.elm = elm
     this.ns = ns
     this.context = context
+    this.functionalContext = undefined
     this.key = data && data.key
     this.componentOptions = componentOptions
     this.child = undefined

+ 36 - 1
test/unit/features/component/component-slot.spec.js

@@ -29,7 +29,7 @@ describe('Component slot', () => {
     expect(child.$el.childNodes.length).toBe(0)
   })
 
-  it('default content', done => {
+  it('default slot', done => {
     mount({
       childTemplate: '<div><slot></slot></div>',
       parentContent: '<p>{{ msg }}</p>'
@@ -43,6 +43,20 @@ describe('Component slot', () => {
     }).then(done)
   })
 
+  it('named slot', done => {
+    mount({
+      childTemplate: '<div><slot name="test"></slot></div>',
+      parentContent: '<p slot="test">{{ msg }}</p>'
+    })
+    expect(child.$el.tagName).toBe('DIV')
+    expect(child.$el.children[0].tagName).toBe('P')
+    expect(child.$el.children[0].textContent).toBe('parent message')
+    vm.msg = 'changed'
+    waitForUpdate(() => {
+      expect(child.$el.children[0].textContent).toBe('changed')
+    }).then(done)
+  })
+
   it('fallback content', () => {
     mount({
       childTemplate: '<div><slot><p>{{msg}}</p></slot></div>'
@@ -536,4 +550,25 @@ describe('Component slot', () => {
     vm.$mount()
     expect('Error when rendering root').not.toHaveBeenWarned()
   })
+
+  // #3872
+  it('functional component as slot', () => {
+    const vm = new Vue({
+      template: `
+        <parent>
+          <child>one</child>
+          <child slot="a">two</child>
+        </parent>
+      `,
+      components: {
+        parent: {
+          template: `<div><slot name="a"></slot><slot></slot></div>`
+        },
+        child: {
+          template: '<div><slot></slot></div>'
+        }
+      }
+    }).$mount()
+    expect(vm.$el.innerHTML.trim()).toBe('<div>two</div><div>one</div>')
+  })
 })