Browse Source

should also teardown props and replacer dirs when unlinking (fix #901)

Evan You 11 years ago
parent
commit
02c6392d1d
3 changed files with 60 additions and 28 deletions
  1. 32 25
      src/compiler/compile.js
  2. 2 1
      src/instance/compile.js
  3. 26 2
      test/unit/specs/compiler/compile_spec.js

+ 32 - 25
src/compiler/compile.js

@@ -52,16 +52,13 @@ exports.compile = function (el, options, partial, host) {
    */
 
   return function compositeLinkFn (vm, el) {
-    // save original directive count before linking
-    // so we can capture the directives created during a
-    // partial compilation.
-    var originalDirCount = vm._directives.length
     // cache childNodes before linking parent, fix #657
     var childNodes = _.toArray(el.childNodes)
     // link
-    if (nodeLinkFn) nodeLinkFn(vm, el, host)
-    if (childLinkFn) childLinkFn(vm, childNodes, host)
-    var dirs = vm._directives.slice(originalDirCount)
+    var dirs = linkAndCapture(function () {
+      if (nodeLinkFn) nodeLinkFn(vm, el, host)
+      if (childLinkFn) childLinkFn(vm, childNodes, host)
+    }, vm)
 
     /**
      * The linker function returns an unlink function that
@@ -76,6 +73,20 @@ exports.compile = function (el, options, partial, host) {
   }
 }
 
+/**
+ * Apply a linker to a vm/element pair and capture the
+ * directives created during the process.
+ *
+ * @param {Function} linker
+ * @param {Vue} vm
+ */
+
+function linkAndCapture (linker, vm) {
+  var originalDirCount = vm._directives.length
+  linker()
+  return vm._directives.slice(originalDirCount)
+}
+
 /**
  * Teardown partial linked directives.
  *
@@ -85,6 +96,7 @@ exports.compile = function (el, options, partial, host) {
  */
 
 function teardownDirs (vm, dirs, destroying) {
+  if (!dirs) return
   var i = dirs.length
   while (i--) {
     dirs[i]._teardown()
@@ -117,15 +129,17 @@ function teardownDirs (vm, dirs, destroying) {
  * @return {Function}
  */
 
- exports.compileRoot = function (vm, el, options) {
+ exports.compileAndLinkRoot = function (vm, el, options) {
   var containerAttrs = options._containerAttrs
   var replacerAttrs = options._replacerAttrs
   var props = options.props
   var propsLinkFn, parentLinkFn, replacerLinkFn
+
   // 1. props
   propsLinkFn = props && containerAttrs
     ? compileProps(el, containerAttrs, props)
     : null
+
   // only need to compile other attributes for
   // non-block instances
   if (el.nodeType !== 11) {
@@ -146,33 +160,26 @@ function teardownDirs (vm, dirs, destroying) {
     }
   }
 
-  // link props
-  if (propsLinkFn) {
-    // explicitly passing null to props
-    // linkers because they don't need a real element
-    propsLinkFn(vm, null)
-  }
-
   // link parent dirs
-  var parentDirs
   var parent = vm.$parent
+  var parentDirs
   if (parent && parentLinkFn) {
-    var originalParentDirCount = parent._directives.length
-    parentLinkFn(parent, el)
-    parentDirs = parent._directives.slice(originalParentDirCount)
+    parentDirs = linkAndCapture(function () {
+      parentLinkFn(parent, el)
+    }, parent)
   }
 
   // link self
-  if (replacerLinkFn) {
-    replacerLinkFn(vm, el)
-  }
+  var selfDirs = linkAndCapture(function () {
+    if (propsLinkFn) propsLinkFn(vm, null)
+    if (replacerLinkFn) replacerLinkFn(vm, el)
+  }, vm)
 
   // return the unlink function that tearsdown parent
   // container directives.
   return function rootUnlinkFn () {
-    if (parentDirs) {
-      teardownDirs(parent, parentDirs)
-    }
+    teardownDirs(parent, parentDirs)
+    teardownDirs(vm, selfDirs)
   }
 }
 

+ 2 - 1
src/instance/compile.js

@@ -34,7 +34,8 @@ exports._compile = function (el) {
 
     // root is always compiled per-instance, because
     // container attrs and props can be different every time.
-    var rootUnlinkFn = compiler.compileRoot(this, el, options)
+    var rootUnlinkFn =
+      compiler.compileAndLinkRoot(this, el, options)
 
     // compile and link the rest
     var linker

+ 26 - 2
test/unit/specs/compiler/compile_spec.js

@@ -168,7 +168,7 @@ if (_.inBrowser) {
       el.setAttribute('with-filter', '{{a | filter}}')
       el.setAttribute('boolean-literal', '{{true}}')
       transclude(el, options)
-      compiler.compileRoot(vm, el, options)
+      compiler.compileAndLinkRoot(vm, el, options)
       // should skip literals and one-time bindings
       expect(vm._bindDir.calls.count()).toBe(5)
       // data-some-attr
@@ -226,7 +226,7 @@ if (_.inBrowser) {
       el.setAttribute('a', 'hi')
       el.setAttribute('b', '{{hi}}')
       transclude(el, options)
-      compiler.compileRoot(vm, el, options)
+      compiler.compileAndLinkRoot(vm, el, options)
       expect(vm._bindDir.calls.count()).toBe(0)
       expect(vm.$set).toHaveBeenCalledWith('a', 'hi')
       expect(hasWarned(_, 'Cannot bind dynamic prop on a root')).toBe(true)
@@ -294,6 +294,30 @@ if (_.inBrowser) {
       expect(childSpy).toHaveBeenCalledWith(2)
     })
 
+    it('should teardown props and replacer directives when unlinking', function () {
+      var vm = new Vue({
+        el: el,
+        template: '<test prop="{{msg}}"></test>',
+        data: {
+          msg: 'hi'
+        },
+        components: {
+          test: {
+            props: ['prop'],
+            template: '<div v-show="true"></div>',
+            replace: true
+          }
+        }
+      })
+      var dirs = vm._children[0]._directives
+      expect(dirs.length).toBe(2)
+      vm._children[0].$destroy()
+      var i = dirs.length
+      while (i--) {
+        expect(dirs[i]._bound).toBe(false)
+      }
+    })
+
     it('should remove parent container directives from parent when unlinking', function () {
       var vm = new Vue({
         el: el,