Przeglądaj źródła

refactor v-ref, allow use in child templates, fix #636

Evan You 11 lat temu
rodzic
commit
df5f6409df

+ 10 - 0
src/directives/component.js

@@ -26,6 +26,8 @@ module.exports = {
       // we simply remove it from the DOM and save it in a
       // cache object, with its constructor id as the key.
       this.keepAlive = this._checkParam('keep-alive') != null
+      // check ref
+      this.refID = _.attr(this.el, 'ref')
       if (this.keepAlive) {
         this.cache = {}
       }
@@ -83,6 +85,10 @@ module.exports = {
       if (this.keepAlive) {
         this.cache[this.ctorId] = child
       }
+      var refID = child._refID || this.refID
+      if (refID) {
+        vm.$[refID] = child
+      }
       return child
     }
   },
@@ -94,6 +100,10 @@ module.exports = {
 
   unbuild: function () {
     var child = this.childVM
+    var refID = (child && child._refID) || this.refID
+    if (refID) {
+      this.vm.$[refID] = null
+    }
     if (!child || this.keepAlive) {
       return
     }

+ 9 - 11
src/directives/ref.js

@@ -5,21 +5,19 @@ module.exports = {
   isLiteral: true,
 
   bind: function () {
-    var child = this.el.__vue__
-    if (!child || this.vm !== child.$parent) {
+    var vm = this.el.__vue__
+    if (!vm) {
       _.warn(
-        'v-ref should only be used on a child component ' +
-        'from the parent template.'
+        'v-ref should only be used on a component root element.'
       )
       return
     }
-    this.vm.$[this.expression] = child
-  },
-
-  unbind: function () {
-    if (this.vm.$[this.expression] === this.el.__vue__) {
-      delete this.vm.$[this.expression]
-    }
+    // If we get here, it means this is a `v-ref` on a
+    // child, because parent scope `v-ref` is stripped in
+    // `v-component` already. So we just record our own ref
+    // here - it will overwrite parent ref in `v-component`,
+    // if any.
+    vm._refID = this.expression
   }
   
 }

+ 7 - 7
src/directives/repeat.js

@@ -69,9 +69,9 @@ module.exports = {
    */
 
   checkRef: function () {
-    var childId = _.attr(this.el, 'ref')
-    this.childId = childId
-      ? this.vm.$interpolate(childId)
+    var refID = _.attr(this.el, 'ref')
+    this.refID = refID
+      ? this.vm.$interpolate(refID)
       : null
     var elId = _.attr(this.el, 'el')
     this.elId = elId
@@ -136,8 +136,8 @@ module.exports = {
     }
     this.vms = this.diff(data || [], this.vms)
     // update v-ref
-    if (this.childId) {
-      this.vm.$[this.childId] = this.vms
+    if (this.refID) {
+      this.vm.$[this.refID] = this.vms
     }
     if (this.elId) {
       this.vm.$$[this.elId] = this.vms.map(function (vm) {
@@ -325,8 +325,8 @@ module.exports = {
    */
 
   unbind: function () {
-    if (this.childId) {
-      delete this.vm.$[this.childId]
+    if (this.refID) {
+      this.vm.$[this.refID] = null
     }
     if (this.vms) {
       var i = this.vms.length

+ 50 - 17
test/unit/specs/directives/ref_spec.js

@@ -13,6 +13,9 @@ if (_.inBrowser) {
     var components = {
       test: {
         id: 'test'
+      },
+      test2: {
+        id: 'test2'
       }
     }
 
@@ -24,8 +27,52 @@ if (_.inBrowser) {
       })
       expect(vm.$.test).toBeTruthy()
       expect(vm.$.test.$options.id).toBe('test')
-      vm.$.test.$destroy()
-      expect(vm.$.test).toBeUndefined()
+    })
+
+    it('with dynamic v-component', function (done) {
+      var vm = new Vue({
+        el: el,
+        components: components,
+        data: { test: 'test' },
+        template: '<div v-component="{{test}}" v-ref="test"></div>'
+      })
+      expect(vm.$.test.$options.id).toBe('test')
+      vm.test = 'test2'
+      _.nextTick(function () {
+        expect(vm.$.test.$options.id).toBe('test2')
+        vm.test = ''
+        _.nextTick(function () {
+          expect(vm.$.test).toBeNull()
+          done()          
+        })
+      })
+    })
+
+    it('should also work in child template', function (done) {
+      var vm = new Vue({
+        el: el,
+        data: { view: 'test1' },
+        template: '<div v-component="{{view}}"></div>',
+        components: {
+          test1: {
+            id: 'test1',
+            template: '<div v-ref="test1"></div>',
+            replace: true
+          },
+          test2: {
+            id: 'test2',
+            template: '<div v-ref="test2"></div>',
+            replace: true
+          }
+        }
+      })
+      expect(vm.$.test1.$options.id).toBe('test1')
+      vm.view = 'test2'
+      _.nextTick(function () {
+        expect(vm.$.test1).toBeNull()
+        expect(vm.$.test2.$options.id).toBe('test2')
+        done()
+      })
     })
 
     it('with v-repeat', function (done) {
@@ -42,7 +89,7 @@ if (_.inBrowser) {
       _.nextTick(function () {
         expect(vm.$.test.length).toBe(0)
         vm._directives[0].unbind()
-        expect(vm.$.test).toBeUndefined()
+        expect(vm.$.test).toBeNull()
         done()
       })
     })
@@ -71,19 +118,5 @@ if (_.inBrowser) {
       expect(_.warn).toHaveBeenCalled()
     })
 
-    it('should warn when used in child template', function () {
-      var vm = new Vue({
-        el: el,
-        template: '<div v-component="test"></div>',
-        components: {
-          test: {
-            template: '<div v-ref="test"></div>',
-            replace: true
-          }
-        }
-      })
-      expect(_.warn).toHaveBeenCalled()
-    })
-
   })
 }