Explorar o código

ensure children is only re-resolved on parent update (fix #3400)

Evan You %!s(int64=9) %!d(string=hai) anos
pai
achega
21908b7267

+ 3 - 1
src/core/instance/lifecycle.js

@@ -4,6 +4,7 @@ import Watcher from '../observer/watcher'
 import { emptyVNode } from '../vdom/vnode'
 import { observerState } from '../observer/index'
 import { warn, validateProp, remove, noop } from '../util/index'
+import { resolveSlots } from './render'
 
 export let activeInstance: any = null
 
@@ -137,8 +138,9 @@ export function lifecycleMixin (Vue: Class<Component>) {
       vm.$options._parentListeners = listeners
       vm._updateListeners(listeners, oldListeners)
     }
-    // force udpate if has children
+    // resolve slots + force update if has children
     if (hasChildren) {
+      vm.$slots = resolveSlots(renderChildren)
       vm.$forceUpdate()
     }
   }

+ 4 - 6
src/core/instance/render.js

@@ -14,7 +14,7 @@ export function initRender (vm: Component) {
   vm.$vnode = null // the placeholder node in parent tree
   vm._vnode = null // the root of the child tree
   vm._staticTrees = null
-  vm.$slots = {}
+  vm.$slots = resolveSlots(vm.$options._renderChildren)
   // bind the public createElement fn to this instance
   // so that we get proper render context inside it.
   vm.$createElement = bind(createElement, vm)
@@ -33,7 +33,6 @@ export function renderMixin (Vue: Class<Component>) {
     const {
       render,
       staticRenderFns,
-      _renderChildren,
       _parentVnode
     } = vm.$options
 
@@ -43,9 +42,6 @@ export function renderMixin (Vue: Class<Component>) {
     // set parent vnode. this allows render functions to have access
     // to the data on the placeholder node.
     vm.$vnode = _parentVnode
-    // resolve slots. becaues slots are rendered in parent scope,
-    // we set the activeInstance to parent.
-    vm.$slots = resolveSlots(_renderChildren)
     // render self
     let vnode
     try {
@@ -171,7 +167,9 @@ export function renderMixin (Vue: Class<Component>) {
   }
 }
 
-export function resolveSlots (renderChildren: ?VNodeChildren): Object {
+export function resolveSlots (
+  renderChildren: ?VNodeChildren
+): { [key: string]: Array<VNode> } {
   const slots = {}
   if (!renderChildren) {
     return slots

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

@@ -313,6 +313,7 @@ describe('Component slot', () => {
     expect('Render function should return a single root node').toHaveBeenWarned()
   })
 
+  // #3254
   it('should not keep slot name when passed further down', () => {
     const vm = new Vue({
       template: '<test><span slot="foo">foo<span></test>',
@@ -321,7 +322,12 @@ describe('Component slot', () => {
           template: '<child><slot name="foo"></slot></child>',
           components: {
             child: {
-              template: '<div><div class="default"><slot></slot></div><div class="named"><slot name="foo"></slot></div></div>'
+              template: `
+                <div>
+                  <div class="default"><slot></slot></div>
+                  <div class="named"><slot name="foo"></slot></div>
+                </div>
+              `
             }
           }
         }
@@ -330,4 +336,80 @@ describe('Component slot', () => {
     expect(vm.$el.querySelector('.default').textContent).toBe('foo')
     expect(vm.$el.querySelector('.named').textContent).toBe('')
   })
+
+  it('should not keep slot name when passed further down (nested)', () => {
+    const vm = new Vue({
+      template: '<wrap><test><span slot="foo">foo<span></test></wrap>',
+      components: {
+        wrap: {
+          template: '<div><slot></slot></div>'
+        },
+        test: {
+          template: '<child><slot name="foo"></slot></child>',
+          components: {
+            child: {
+              template: `
+                <div>
+                  <div class="default"><slot></slot></div>
+                  <div class="named"><slot name="foo"></slot></div>
+                </div>
+              `
+            }
+          }
+        }
+      }
+    }).$mount()
+    expect(vm.$el.querySelector('.default').textContent).toBe('foo')
+    expect(vm.$el.querySelector('.named').textContent).toBe('')
+  })
+
+  it('should not keep slot name when passed further down (functional)', () => {
+    const child = {
+      template: `
+        <div>
+          <div class="default"><slot></slot></div>
+          <div class="named"><slot name="foo"></slot></div>
+        </div>
+      `
+    }
+    const vm = new Vue({
+      template: '<test><span slot="foo">foo<span></test>',
+      components: {
+        test: {
+          functional: true,
+          render (h, ctx) {
+            const slots = ctx.slots()
+            return h(child, slots.foo)
+          }
+        }
+      }
+    }).$mount()
+    console.log(vm.$el.innerHTML)
+    expect(vm.$el.querySelector('.default').textContent).toBe('foo')
+    expect(vm.$el.querySelector('.named').textContent).toBe('')
+  })
+
+  // #3400
+  it('named slots should be consistent across re-renders', done => {
+    const vm = new Vue({
+      template: `
+        <comp>
+          <div slot="foo">foo</div>
+        </comp>
+      `,
+      components: {
+        comp: {
+          data () {
+            return { a: 1 }
+          },
+          template: `<div><slot name="foo"></slot>{{ a }}</div>`
+        }
+      }
+    }).$mount()
+    expect(vm.$el.textContent).toBe('foo1')
+    vm.$children[0].a = 2
+    waitForUpdate(() => {
+      expect(vm.$el.textContent).toBe('foo2')
+    }).then(done)
+  })
 })