Просмотр исходного кода

feat: support denoting normal elements as scoped slot

also deprecate "scope" in favor of "slot-scope"
Evan You 8 лет назад
Родитель
Сommit
dae173d96d

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

@@ -239,7 +239,8 @@ export function genData (el: ASTElement, state: CodegenState): string {
     data += `${genHandlers(el.nativeEvents, true, state.warn)},`
   }
   // slot target
-  if (el.slotTarget) {
+  // only for non-scoped slots
+  if (el.slotTarget && !el.slotScope) {
     data += `slot:${el.slotTarget},`
   }
   // scoped slots
@@ -342,7 +343,7 @@ function genScopedSlot (
   if (el.for && !el.forProcessed) {
     return genForScopedSlot(key, el, state)
   }
-  return `{key:${key},fn:function(${String(el.attrsMap.scope)}){` +
+  return `{key:${key},fn:function(${String(el.slotScope)}){` +
     `return ${el.tag === 'template'
       ? genChildren(el, state) || 'void 0'
       : genElement(el, state)

+ 22 - 5
src/compiler/parser/index.js

@@ -31,7 +31,7 @@ const modifierRE = /\.[^.]+/g
 const decodeHTMLCached = cached(he.decode)
 
 // configurable state
-export let warn
+export let warn: any
 let delimiters
 let transforms
 let preTransforms
@@ -428,14 +428,31 @@ function processSlot (el) {
       )
     }
   } else {
+    let slotScope
+    if (el.tag === 'template') {
+      slotScope = getAndRemoveAttr(el, 'scope')
+      /* istanbul ignore if */
+      if (process.env.NODE_ENV !== 'production' && slotScope) {
+        warn(
+          `the "scope" attribute for scoped slots have been deprecated and ` +
+          `replaced by "slot-scope" since 2.5. The new "slot-scope" attribute ` +
+          `can also be used on plain elements in addition to <template> to ` +
+          `denote scoped slots.`,
+          true
+        )
+      }
+      el.slotScope = slotScope || getAndRemoveAttr(el, 'slot-scope')
+    } else if ((slotScope = getAndRemoveAttr(el, 'slot-scope'))) {
+      el.slotScope = slotScope
+    }
     const slotTarget = getBindingAttr(el, 'slot')
     if (slotTarget) {
       el.slotTarget = slotTarget === '""' ? '"default"' : slotTarget
       // preserve slot as an attribute for native shadow DOM compat
-      addAttr(el, 'slot', slotTarget)
-    }
-    if (el.tag === 'template') {
-      el.slotScope = getAndRemoveAttr(el, 'scope')
+      // only for non-scoped slots.
+      if (!el.slotScope) {
+        addAttr(el, 'slot', slotTarget)
+      }
     }
   }
 }

+ 1 - 1
src/shared/util.js

@@ -106,7 +106,7 @@ export const isBuiltInTag = makeMap('slot,component', true)
 /**
  * Check if a attribute is a reserved attribute.
  */
-export const isReservedAttribute = makeMap('key,ref,slot,is')
+export const isReservedAttribute = makeMap('key,ref,slot,slot-scope,is')
 
 /**
  * Remove an item from an array

+ 98 - 12
test/unit/features/component/component-scoped-slot.spec.js

@@ -5,7 +5,7 @@ describe('Component scoped slot', () => {
     const vm = new Vue({
       template: `
         <test ref="test">
-          <template scope="props">
+          <template slot-scope="props">
             <span>{{ props.msg }}</span>
           </template>
         </test>
@@ -31,11 +31,39 @@ describe('Component scoped slot', () => {
     }).then(done)
   })
 
+  it('default slot (plain element)', done => {
+    const vm = new Vue({
+      template: `
+        <test ref="test">
+          <span slot-scope="props">{{ props.msg }}</span>
+        </test>
+      `,
+      components: {
+        test: {
+          data () {
+            return { msg: 'hello' }
+          },
+          template: `
+            <div>
+              <slot :msg="msg"></slot>
+            </div>
+          `
+        }
+      }
+    }).$mount()
+
+    expect(vm.$el.innerHTML).toBe('<span>hello</span>')
+    vm.$refs.test.msg = 'world'
+    waitForUpdate(() => {
+      expect(vm.$el.innerHTML).toBe('<span>world</span>')
+    }).then(done)
+  })
+
   it('with v-bind', done => {
     const vm = new Vue({
       template: `
         <test ref="test">
-          <template scope="props">
+          <template slot-scope="props">
             <span>{{ props.msg }} {{ props.msg2 }} {{ props.msg3 }}</span>
           </template>
         </test>
@@ -65,11 +93,11 @@ describe('Component scoped slot', () => {
     }).then(done)
   })
 
-  it('template slot', done => {
+  it('named scoped slot', done => {
     const vm = new Vue({
       template: `
         <test ref="test">
-          <template slot="item" scope="props">
+          <template slot="item" slot-scope="props">
             <span>{{ props.foo }}</span><span>{{ props.bar }}</span>
           </template>
         </test>
@@ -95,6 +123,34 @@ describe('Component scoped slot', () => {
     }).then(done)
   })
 
+  it('named scoped slot (plain element)', done => {
+    const vm = new Vue({
+      template: `
+        <test ref="test">
+          <span slot="item" slot-scope="props">{{ props.foo }} {{ props.bar }}</span>
+        </test>
+      `,
+      components: {
+        test: {
+          data () {
+            return { foo: 'FOO', bar: 'BAR' }
+          },
+          template: `
+            <div>
+              <slot name="item" :foo="foo" :bar="bar"></slot>
+            </div>
+          `
+        }
+      }
+    }).$mount()
+
+    expect(vm.$el.innerHTML).toBe('<span>FOO BAR</span>')
+    vm.$refs.test.foo = 'BAZ'
+    waitForUpdate(() => {
+      expect(vm.$el.innerHTML).toBe('<span>BAZ BAR</span>')
+    }).then(done)
+  })
+
   it('fallback content', () => {
     const vm = new Vue({
       template: `<test></test>`,
@@ -120,7 +176,7 @@ describe('Component scoped slot', () => {
     const vm = new Vue({
       template: `
         <test ref="test">
-          <template slot="item" scope="props">
+          <template slot="item" slot-scope="props">
             <span>{{ props.text }}</span>
           </template>
         </test>
@@ -158,7 +214,7 @@ describe('Component scoped slot', () => {
     const vm = new Vue({
       template: `
         <test ref="test">
-          <template slot="item" scope="props">
+          <template slot="item" slot-scope="props">
             <span>{{ props.text }}</span>
           </template>
         </test>
@@ -221,7 +277,7 @@ describe('Component scoped slot', () => {
     const vm = new Vue({
       template: `
         <test ref="test">
-          <template slot="item" scope="props">
+          <template slot="item" slot-scope="props">
             <span>{{ props.text || 'meh' }}</span>
           </template>
         </test>
@@ -246,7 +302,7 @@ describe('Component scoped slot', () => {
     new Vue({
       template: `
         <test ref="test">
-          <template slot="item" scope="props">
+          <template slot="item" slot-scope="props">
             <span>{{ props.text }}</span>
           </template>
         </test>
@@ -343,8 +399,8 @@ describe('Component scoped slot', () => {
       },
       template: `
         <child>
-          <template :slot="a" scope="props">A {{ props.msg }}</template>
-          <template :slot="b" scope="props">B {{ props.msg }}</template>
+          <template :slot="a" slot-scope="props">A {{ props.msg }}</template>
+          <template :slot="b" slot-scope="props">B {{ props.msg }}</template>
         </child>
       `,
       components: { Child }
@@ -389,10 +445,10 @@ describe('Component scoped slot', () => {
       data: { names: ['foo', 'bar'] },
       template: `
         <test ref="test">
-          <template v-for="n in names" :slot="n" scope="props">
+          <template v-for="n in names" :slot="n" slot-scope="props">
             <span>{{ props.msg }}</span>
           </template>
-          <template slot="abc" scope="props">
+          <template slot="abc" slot-scope="props">
             <span>{{ props.msg }}</span>
           </template>
         </test>
@@ -417,4 +473,34 @@ describe('Component scoped slot', () => {
       expect(vm.$el.innerHTML).toBe('<span>world foo</span> <span>world bar</span> <span>world abc</span>')
     }).then(done)
   })
+
+  it('scoped slot with v-for (plain elements)', done => {
+    const vm = new Vue({
+      data: { names: ['foo', 'bar'] },
+      template: `
+        <test ref="test">
+          <span v-for="n in names" :slot="n" slot-scope="props">{{ props.msg }}</span>
+          <span slot="abc" slot-scope="props">{{ props.msg }}</span>
+        </test>
+      `,
+      components: {
+        test: {
+          data: () => ({ msg: 'hello' }),
+          template: `
+            <div>
+              <slot name="foo" :msg="msg + ' foo'"></slot>
+              <slot name="bar" :msg="msg + ' bar'"></slot>
+              <slot name="abc" :msg="msg + ' abc'"></slot>
+            </div>
+          `
+        }
+      }
+    }).$mount()
+
+    expect(vm.$el.innerHTML).toBe('<span>hello foo</span> <span>hello bar</span> <span>hello abc</span>')
+    vm.$refs.test.msg = 'world'
+    waitForUpdate(() => {
+      expect(vm.$el.innerHTML).toBe('<span>world foo</span> <span>world bar</span> <span>world abc</span>')
+    }).then(done)
+  })
 })

+ 22 - 0
test/unit/modules/compiler/codegen.spec.js

@@ -179,6 +179,28 @@ describe('codegen', () => {
     )
   })
 
+  it('generate scoped slot', () => {
+    assertCodegen(
+      '<foo><template slot-scope="bar">{{ bar }}</template></foo>',
+      `with(this){return _c('foo',{scopedSlots:_u([{key:"default",fn:function(bar){return [_v(_s(bar))]}}])})}`
+    )
+    assertCodegen(
+      '<foo><div slot-scope="bar">{{ bar }}</div></foo>',
+      `with(this){return _c('foo',{scopedSlots:_u([{key:"default",fn:function(bar){return _c('div',{},[_v(_s(bar))])}}])})}`
+    )
+  })
+
+  it('generate named scoped slot', () => {
+    assertCodegen(
+      '<foo><template slot="foo" slot-scope="bar">{{ bar }}</template></foo>',
+      `with(this){return _c('foo',{scopedSlots:_u([{key:"foo",fn:function(bar){return [_v(_s(bar))]}}])})}`
+    )
+    assertCodegen(
+      '<foo><div slot="foo" slot-scope="bar">{{ bar }}</div></foo>',
+      `with(this){return _c('foo',{scopedSlots:_u([{key:"foo",fn:function(bar){return _c('div',{},[_v(_s(bar))])}}])})}`
+    )
+  })
+
   it('generate class binding', () => {
     // static
     assertCodegen(