Explorar o código

fix scoped slots with dynamic slot names + force update for child components with scoped slots (fix #4779)

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

+ 2 - 0
flow/component.js

@@ -104,6 +104,8 @@ declare interface Component {
   _b: (data: any, value: any, asProp?: boolean) => VNodeData;
   // check custom keyCode
   _k: (eventKeyCode: number, key: string, builtInAlias: number | Array<number> | void) => boolean;
+  // resolve scoped slots
+  _u: (scopedSlots: Array<[string, Function]>) => { [key: string]: Function };
 
   // allow dynamic method registration
   [key: string]: any

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

@@ -273,17 +273,17 @@ function genInlineTemplate (el: ASTElement): ?string {
 }
 
 function genScopedSlots (slots: { [key: string]: ASTElement }): string {
-  return `scopedSlots:{${
+  return `scopedSlots:_u([${
     Object.keys(slots).map(key => genScopedSlot(key, slots[key])).join(',')
-  }}`
+  }])`
 }
 
 function genScopedSlot (key: string, el: ASTElement) {
-  return `${key}:function(${String(el.attrsMap.scope)}){` +
+  return `[${key},function(${String(el.attrsMap.scope)}){` +
     `return ${el.tag === 'template'
       ? genChildren(el) || 'void 0'
       : genElement(el)
-  }}`
+  }}]`
 }
 
 function genChildren (el: ASTElement, checkSkip?: boolean): string | void {

+ 1 - 1
src/compiler/parser/index.js

@@ -176,7 +176,7 @@ export function parse (
           processIfConditions(element, currentParent)
         } else if (element.slotScope) { // scoped slot
           currentParent.plain = false
-          const name = element.slotTarget || 'default'
+          const name = element.slotTarget || '"default"'
           ;(currentParent.scopedSlots || (currentParent.scopedSlots = {}))[name] = element
         } else {
           currentParent.children.push(element)

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

@@ -3,9 +3,9 @@
 import Watcher from '../observer/watcher'
 import { createEmptyVNode } from '../vdom/vnode'
 import { observerState } from '../observer/index'
-import { warn, validateProp, remove, noop } from '../util/index'
-import { resolveSlots } from './render-helpers/resolve-slots'
 import { updateComponentListeners } from './events'
+import { resolveSlots } from './render-helpers/resolve-slots'
+import { warn, validateProp, remove, noop, emptyObject } from '../util/index'
 
 export let activeInstance: any = null
 
@@ -120,13 +120,23 @@ export function lifecycleMixin (Vue: Class<Component>) {
     renderChildren: ?Array<VNode>
   ) {
     const vm: Component = this
-    const hasChildren = !!(vm.$options._renderChildren || renderChildren)
+
+    // determine whether component has slot children
+    // we need to do this before overwriting $options._renderChildren
+    const hasChildren = !!(
+      renderChildren ||               // has new static slots
+      vm.$options._renderChildren ||  // has old static slots
+      parentVnode.data.scopedSlots || // has new scoped slots
+      vm.$scopedSlots !== emptyObject // has old scoped slots
+    )
+
     vm.$options._parentVnode = parentVnode
     vm.$vnode = parentVnode // update vm's placeholder node without re-render
     if (vm._vnode) { // update child tree's parent
       vm._vnode.parent = parentVnode
     }
     vm.$options._renderChildren = renderChildren
+
     // update props
     if (propsData && vm.$options.props) {
       observerState.shouldConvert = false

+ 10 - 0
src/core/instance/render-helpers/resolve-slots.js

@@ -38,3 +38,13 @@ export function resolveSlots (
   }
   return slots
 }
+
+export function resolveScopedSlots (
+  fns: Array<[string, Function]>
+): { [key: string]: Function } {
+  const res = {}
+  for (let i = 0; i < fns.length; i++) {
+    res[fns[i][0]] = fns[i][1]
+  }
+  return res
+}

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

@@ -8,6 +8,7 @@ import {
   toNumber,
   _toString,
   looseEqual,
+  emptyObject,
   looseIndexOf,
   formatComponentName
 } from '../util/index'
@@ -21,11 +22,11 @@ import VNode, {
 import { createElement } from '../vdom/create-element'
 import { renderList } from './render-helpers/render-list'
 import { renderSlot } from './render-helpers/render-slot'
-import { resolveSlots } from './render-helpers/resolve-slots'
 import { resolveFilter } from './render-helpers/resolve-filter'
 import { checkKeyCodes } from './render-helpers/check-keycodes'
 import { bindObjectProps } from './render-helpers/bind-object-props'
 import { renderStatic, markOnce } from './render-helpers/render-static'
+import { resolveSlots, resolveScopedSlots } from './render-helpers/resolve-slots'
 
 export function initRender (vm: Component) {
   vm.$vnode = null // the placeholder node in parent tree
@@ -34,7 +35,7 @@ export function initRender (vm: Component) {
   const parentVnode = vm.$options._parentVnode
   const renderContext = parentVnode && parentVnode.context
   vm.$slots = resolveSlots(vm.$options._renderChildren, renderContext)
-  vm.$scopedSlots = {}
+  vm.$scopedSlots = emptyObject
   // bind the createElement fn to this instance
   // so that we get proper render context inside it.
   // args order: tag, data, children, normalizationType, alwaysNormalize
@@ -65,9 +66,7 @@ export function renderMixin (Vue: Class<Component>) {
       }
     }
 
-    if (_parentVnode && _parentVnode.data.scopedSlots) {
-      vm.$scopedSlots = _parentVnode.data.scopedSlots
-    }
+    vm.$scopedSlots = (_parentVnode && _parentVnode.data.scopedSlots) || emptyObject
 
     if (staticRenderFns && !vm._staticTrees) {
       vm._staticTrees = []
@@ -124,4 +123,5 @@ export function renderMixin (Vue: Class<Component>) {
   Vue.prototype._b = bindObjectProps
   Vue.prototype._v = createTextVNode
   Vue.prototype._e = createEmptyVNode
+  Vue.prototype._u = resolveScopedSlots
 }

+ 2 - 0
src/core/util/lang.js

@@ -1,5 +1,7 @@
 /* @flow */
 
+export const emptyObject = Object.freeze({})
+
 /**
  * Check if a string starts with $ or _
  */

+ 35 - 0
test/unit/features/component/component-scoped-slot.spec.js

@@ -325,6 +325,41 @@ describe('Component scoped slot', () => {
     expect(vm.$el.innerHTML).toBe('<span>hello</span>')
   })
 
+  // #4779
+  it('should support dynamic slot target', done => {
+    const Child = {
+      template: `
+        <div>
+          <slot name="a" msg="a" />
+          <slot name="b" msg="b" />
+        </div>
+      `
+    }
+
+    const vm = new Vue({
+      data: {
+        a: 'a',
+        b: 'b'
+      },
+      template: `
+        <child>
+          <template :slot="a" scope="props">A {{ props.msg }}</template>
+          <template :slot="b" scope="props">B {{ props.msg }}</template>
+        </child>
+      `,
+      components: { Child }
+    }).$mount()
+
+    expect(vm.$el.textContent.trim()).toBe('A a B b')
+
+    // switch slots
+    vm.a = 'b'
+    vm.b = 'a'
+    waitForUpdate(() => {
+      expect(vm.$el.textContent.trim()).toBe('B a A b')
+    }).then(done)
+  })
+
   it('render function usage (JSX)', () => {
     const vm = new Vue({
       render (h) {