Browse Source

fix(v-on): correctly remove once listener (#8036)

fix #8032
laoxiong 7 years ago
parent
commit
19c33a7e40

+ 13 - 7
src/core/instance/events.js

@@ -21,25 +21,31 @@ export function initEvents (vm: Component) {
 
 let target: any
 
-function add (event, fn, once) {
-  if (once) {
-    target.$once(event, fn)
-  } else {
-    target.$on(event, fn)
-  }
+function add (event, fn) {
+  target.$on(event, fn)
 }
 
 function remove (event, fn) {
   target.$off(event, fn)
 }
 
+function createOnceHandler (event, fn) {
+  const _target = target
+  return function onceHandler () {
+    const res = fn.apply(null, arguments)
+    if (res !== null) {
+      _target.$off(event, onceHandler)
+    }
+  }
+}
+
 export function updateComponentListeners (
   vm: Component,
   listeners: Object,
   oldListeners: ?Object
 ) {
   target = vm
-  updateListeners(listeners, oldListeners || {}, add, remove, vm)
+  updateListeners(listeners, oldListeners || {}, add, remove, createOnceHandler, vm)
   target = undefined
 }
 

+ 12 - 2
src/core/vdom/helpers/update-listeners.js

@@ -1,7 +1,13 @@
 /* @flow */
 
 import { warn } from 'core/util/index'
-import { cached, isUndef, isPlainObject } from 'shared/util'
+
+import {
+  cached,
+  isUndef,
+  isTrue,
+  isPlainObject
+} from 'shared/util'
 
 const normalizeEvent = cached((name: string): {
   name: string,
@@ -47,6 +53,7 @@ export function updateListeners (
   oldOn: Object,
   add: Function,
   remove: Function,
+  createOnceHandler: Function,
   vm: Component
 ) {
   let name, def, cur, old, event
@@ -68,7 +75,10 @@ export function updateListeners (
       if (isUndef(cur.fns)) {
         cur = on[name] = createFnInvoker(cur)
       }
-      add(event.name, cur, event.once, event.capture, event.passive, event.params)
+      if (isTrue(event.once)) {
+        cur = on[name] = createOnceHandler(event.name, cur, event.capture)
+      }
+      add(event.name, cur, event.capture, event.passive, event.params)
     } else if (cur !== old) {
       old.fns = cur
       on[name] = old

+ 2 - 4
src/platforms/web/runtime/modules/events.js

@@ -28,7 +28,7 @@ function normalizeEvents (on) {
 
 let target: any
 
-function createOnceHandler (handler, event, capture) {
+function createOnceHandler (event, handler, capture) {
   const _target = target // save current target element in closure
   return function onceHandler () {
     const res = handler.apply(null, arguments)
@@ -41,12 +41,10 @@ function createOnceHandler (handler, event, capture) {
 function add (
   event: string,
   handler: Function,
-  once: boolean,
   capture: boolean,
   passive: boolean
 ) {
   handler = withMacroTask(handler)
-  if (once) handler = createOnceHandler(handler, event, capture)
   target.addEventListener(
     event,
     handler,
@@ -77,7 +75,7 @@ function updateDOMListeners (oldVnode: VNodeWithData, vnode: VNodeWithData) {
   const oldOn = oldVnode.data.on || {}
   target = vnode.elm
   normalizeEvents(on)
-  updateListeners(on, oldOn, add, remove, vnode.context)
+  updateListeners(on, oldOn, add, remove, createOnceHandler, vnode.context)
   target = undefined
 }
 

+ 11 - 14
src/platforms/weex/runtime/modules/events.js

@@ -4,10 +4,19 @@ import { updateListeners } from 'core/vdom/helpers/update-listeners'
 
 let target: any
 
+function createOnceHandler (event, handler, capture) {
+  const _target = target // save current target element in closure
+  return function onceHandler () {
+    const res = handler.apply(null, arguments)
+    if (res !== null) {
+      remove(event, onceHandler, capture, _target)
+    }
+  }
+}
+
 function add (
   event: string,
   handler: Function,
-  once: boolean,
   capture: boolean,
   passive?: boolean,
   params?: Array<any>
@@ -16,18 +25,6 @@ function add (
     console.log('Weex do not support event in bubble phase.')
     return
   }
-  if (once) {
-    const oldHandler = handler
-    const _target = target // save current target element in closure
-    handler = function (ev) {
-      const res = arguments.length === 1
-        ? oldHandler(ev)
-        : oldHandler.apply(null, arguments)
-      if (res !== null) {
-        remove(event, null, null, _target)
-      }
-    }
-  }
   target.addEvent(event, handler, params)
 }
 
@@ -47,7 +44,7 @@ function updateDOMListeners (oldVnode: VNodeWithData, vnode: VNodeWithData) {
   const on = vnode.data.on || {}
   const oldOn = oldVnode.data.on || {}
   target = vnode.elm
-  updateListeners(on, oldOn, add, remove, vnode.context)
+  updateListeners(on, oldOn, add, remove, createOnceHandler, vnode.context)
   target = undefined
 }
 

+ 27 - 0
test/unit/features/directives/on.spec.js

@@ -895,4 +895,31 @@ describe('Directive v-on', () => {
     }).$mount()
     expect(`v-on without argument expects an Object value`).toHaveBeenWarned()
   })
+
+  it('should correctly remove once listener', done => {
+    const vm = new Vue({
+      template: `
+        <div>
+          <span v-if="ok" @click.once="foo">
+            a
+          </span>
+          <span v-else a="a">
+            b
+          </span>
+        </div>
+      `,
+      data: {
+        ok: true
+      },
+      methods: {
+        foo: spy
+      }
+    }).$mount()
+
+    vm.ok = false
+    waitForUpdate(() => {
+      triggerEvent(vm.$el.childNodes[0], 'click')
+      expect(spy.calls.count()).toBe(0)
+    }).then(done)
+  })
 })