Jelajahi Sumber

vdom: teardown stale directives on patch (fix #3491)

Evan You 9 tahun lalu
induk
melakukan
eef040ebd0

+ 6 - 1
src/core/vdom/modules/directives.js

@@ -8,6 +8,11 @@ export default {
   },
   update: function updateDirectives (oldVnode: VNodeWithData, vnode: VNodeWithData) {
     applyDirectives(oldVnode, vnode, 'update')
+    // if old vnode has directives but new vnode doesn't
+    // we need to teardown the directives on the old one.
+    if (oldVnode.data.directives && !vnode.data.directives) {
+      applyDirectives(oldVnode, oldVnode, 'unbind')
+    }
   },
   postpatch: function postupdateDirectives (oldVnode: VNodeWithData, vnode: VNodeWithData) {
     applyDirectives(oldVnode, vnode, 'componentUpdated')
@@ -27,7 +32,7 @@ function applyDirectives (
   const dirs = vnode.data.directives
   if (dirs) {
     const oldDirs = oldVnode.data.directives
-    const isUpdate = hook === 'update'
+    const isUpdate = hook === 'update' || hook === 'componentUpdated'
     for (let i = 0; i < dirs.length; i++) {
       const dir = dirs[i]
       const def = resolveAsset(vnode.context.$options, 'directives', dir.name, true)

+ 1 - 0
src/entries/web-runtime-with-compiler.js

@@ -17,6 +17,7 @@ Vue.prototype.$mount = function (
 ): Component {
   el = el && query(el)
 
+  /* istanbul ignore if */
   if (el === document.body || el === document.documentElement) {
     process.env.NODE_ENV !== 'production' && warn(
       `Do not mount Vue to <html> or <body> - mount to normal elements instead.`

+ 26 - 0
test/unit/features/options/directives.spec.js

@@ -114,6 +114,32 @@ describe('Options directives', () => {
     }).then(done)
   })
 
+  it('should teardown directives on old vnodes when new vnodes have none', done => {
+    const vm = new Vue({
+      data: {
+        ok: true
+      },
+      template: `
+        <div>
+          <div v-if="ok" v-test>a</div>
+          <div v-else class="b">b</div>
+        </div>
+      `,
+      directives: {
+        test: {
+          bind: el => { el.id = 'a' },
+          unbind: el => { el.id = '' }
+        }
+      }
+    }).$mount()
+    expect(vm.$el.children[0].id).toBe('a')
+    vm.ok = false
+    waitForUpdate(() => {
+      expect(vm.$el.children[0].id).toBe('')
+      expect(vm.$el.children[0].className).toBe('b')
+    }).then(done)
+  })
+
   it('warn non-existent', () => {
     new Vue({
       template: '<div v-test></div>'