Skip to content

Commit d4b6041

Browse files
committed
fix(v-model): ensure v-model listener trigger before listener with modifier
This reverts commit 62405aa. Because after v2.6.0, nextTick always use microtask. There will be an update between 2 listener and block the v-model listener to update. We need to ensure the listener of v-model is triggered before listener with modifier to fix #11925.
1 parent e20581f commit d4b6041

File tree

2 files changed

+36
-4
lines changed

2 files changed

+36
-4
lines changed

src/core/vdom/helpers/update-listeners.js

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313

1414
const normalizeEvent = cached((name: string): {
1515
name: string,
16+
plain: boolean,
1617
once: boolean,
1718
capture: boolean,
1819
passive: boolean,
@@ -25,8 +26,10 @@ const normalizeEvent = cached((name: string): {
2526
name = once ? name.slice(1) : name
2627
const capture = name.charAt(0) === '!'
2728
name = capture ? name.slice(1) : name
29+
const plain = !(passive || once || capture)
2830
return {
2931
name,
32+
plain,
3033
once,
3134
capture,
3235
passive
@@ -50,6 +53,11 @@ export function createFnInvoker (fns: Function | Array<Function>, vm: ?Component
5053
return invoker
5154
}
5255

56+
// #6552, #11925
57+
function prioritizePlainEvents (a, b) {
58+
return a.plain ? -1 : b.plain ? 1 : 0
59+
}
60+
5361
export function updateListeners (
5462
on: Object,
5563
oldOn: Object,
@@ -59,6 +67,8 @@ export function updateListeners (
5967
vm: Component
6068
) {
6169
let name, def, cur, old, event
70+
const toAdd = []
71+
let hasModifier = false
6272
for (name in on) {
6373
def = cur = on[name]
6474
old = oldOn[name]
@@ -68,6 +78,7 @@ export function updateListeners (
6878
cur = def.handler
6979
event.params = def.params
7080
}
81+
if (!event.plain) hasModifier = true
7182
if (isUndef(cur)) {
7283
process.env.NODE_ENV !== 'production' && warn(
7384
`Invalid handler for event "${event.name}": got ` + String(cur),
@@ -80,12 +91,20 @@ export function updateListeners (
8091
if (isTrue(event.once)) {
8192
cur = on[name] = createOnceHandler(event.name, cur, event.capture)
8293
}
83-
add(event.name, cur, event.capture, event.passive, event.params)
94+
event.handler = cur
95+
toAdd.push(event)
8496
} else if (cur !== old) {
8597
old.fns = cur
8698
on[name] = old
8799
}
88100
}
101+
if (toAdd.length) {
102+
if (hasModifier) toAdd.sort(prioritizePlainEvents)
103+
for (let i = 0; i < toAdd.length; i++) {
104+
const event = toAdd[i]
105+
add(event.name, cur, event.capture, event.passive, event.params)
106+
}
107+
}
89108
for (name in oldOn) {
90109
if (isUndef(on[name])) {
91110
event = normalizeEvent(name)

test/unit/features/directives/model-text.spec.js

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -380,9 +380,20 @@ describe('Directive v-model text', () => {
380380
})
381381

382382
// #6552
383-
// This was original introduced due to the microtask between DOM events issue
384-
// but fixed after switching to MessageChannel.
385-
it('should not block input when another input listener with modifier is used', done => {
383+
// Root cause: input listeners with modifiers are added as a separate
384+
// DOM listener. If a change is triggered inside this listener, an update
385+
// will happen before the second listener is fired! (obviously microtasks
386+
// can be processed in between DOM events on the same element)
387+
// This causes the domProps module to receive state that has not been
388+
// updated by v-model yet (because v-model's listener has not fired yet)
389+
// Solution: make sure to always fire v-model's listener first
390+
// #11925
391+
// triggerEvent(vm.$refs.input, 'input') method will trigger an update after two listener,
392+
// not after the first listener, before the second listener.
393+
// So we should also test whether the v-model listener is triggered
394+
// before the listener with modifier by the variable vModelTriggerAtFirst.
395+
it('listener of v-model should trigger before listener with modifier', done => {
396+
let vModelTriggerAtFirst = false
386397
const vm = new Vue({
387398
data: {
388399
a: 'a',
@@ -396,6 +407,7 @@ describe('Directive v-model text', () => {
396407
`,
397408
methods: {
398409
onInput (e) {
410+
vModelTriggerAtFirst = this.a === 'b'
399411
this.foo = true
400412
}
401413
}
@@ -411,6 +423,7 @@ describe('Directive v-model text', () => {
411423
setTimeout(() => {
412424
expect(vm.a).toBe('b')
413425
expect(vm.$refs.input.value).toBe('b')
426+
expect(vModelTriggerAtFirst).toBe(true)
414427
done()
415428
}, 16)
416429
})

0 commit comments

Comments
 (0)