Browse Source

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

fix #8032
dev
laoxiong 6 years ago
committed by Evan You
parent
commit
19c33a7e40
  1. 20
      src/core/instance/events.js
  2. 14
      src/core/vdom/helpers/update-listeners.js
  3. 6
      src/platforms/web/runtime/modules/events.js
  4. 25
      src/platforms/weex/runtime/modules/events.js
  5. 27
      test/unit/features/directives/on.spec.js

20
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
}

14
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

6
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
}

25
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
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)
})
})

Loading…
Cancel
Save