mirror of https://github.com/vuejs/core.git
fix(vModel): avoid updates caused by side effects of the click
This commit is contained in:
parent
770ea67a9c
commit
3ee62bae0e
|
@ -122,6 +122,9 @@ export const vModelCheckbox: ModelDirective<HTMLInputElement> = {
|
|||
deep: true,
|
||||
created(el, _, vnode) {
|
||||
el[assignKey] = getModelAssigner(vnode)
|
||||
addEventListener(el, 'mousedown', () => {
|
||||
;(el as any)._willChange = true
|
||||
})
|
||||
addEventListener(el, 'change', () => {
|
||||
const modelValue = (el as any)._modelValue
|
||||
const elementValue = getValue(el)
|
||||
|
@ -153,6 +156,10 @@ export const vModelCheckbox: ModelDirective<HTMLInputElement> = {
|
|||
// set initial checked on mount to wait for true-value/false-value
|
||||
mounted: setChecked,
|
||||
beforeUpdate(el, binding, vnode) {
|
||||
if ((el as any)._willChange) {
|
||||
;(el as any)._willChange = false
|
||||
return
|
||||
}
|
||||
el[assignKey] = getModelAssigner(vnode)
|
||||
setChecked(el, binding, vnode)
|
||||
},
|
||||
|
@ -160,7 +167,7 @@ export const vModelCheckbox: ModelDirective<HTMLInputElement> = {
|
|||
|
||||
function setChecked(
|
||||
el: HTMLInputElement,
|
||||
{ value, oldValue }: DirectiveBinding,
|
||||
{ value }: DirectiveBinding,
|
||||
vnode: VNode,
|
||||
) {
|
||||
// store the v-model value on the element so it can be accessed by the
|
||||
|
@ -173,7 +180,6 @@ function setChecked(
|
|||
} else if (isSet(value)) {
|
||||
checked = value.has(vnode.props!.value)
|
||||
} else {
|
||||
if (value === oldValue) return
|
||||
checked = looseEqual(value, getCheckboxValue(el, true))
|
||||
}
|
||||
|
||||
|
@ -204,6 +210,9 @@ export const vModelSelect: ModelDirective<HTMLSelectElement, 'number'> = {
|
|||
deep: true,
|
||||
created(el, { value, modifiers: { number } }, vnode) {
|
||||
const isSetModel = isSet(value)
|
||||
addEventListener(el, 'mousedown', () => {
|
||||
;(el as any)._willChange = true
|
||||
})
|
||||
addEventListener(el, 'change', () => {
|
||||
const selectedVal = Array.prototype.filter
|
||||
.call(el.options, (o: HTMLOptionElement) => o.selected)
|
||||
|
@ -234,6 +243,10 @@ export const vModelSelect: ModelDirective<HTMLSelectElement, 'number'> = {
|
|||
},
|
||||
updated(el, { value }) {
|
||||
if (!el._assigning) {
|
||||
if ((el as any)._willChange) {
|
||||
;(el as any)._willChange = false
|
||||
return
|
||||
}
|
||||
setSelected(el, value)
|
||||
}
|
||||
},
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import path from 'node:path'
|
||||
import { setupPuppeteer } from './e2eUtils'
|
||||
|
||||
const { page, click, isChecked } = setupPuppeteer()
|
||||
const { page, click, isChecked, html, value } = setupPuppeteer()
|
||||
import { nextTick } from 'vue'
|
||||
|
||||
beforeEach(async () => {
|
||||
|
@ -55,3 +55,94 @@ test('checkbox click with v-model', async () => {
|
|||
expect(await isChecked('#first')).toBe(false)
|
||||
expect(await isChecked('#second')).toBe(true)
|
||||
})
|
||||
|
||||
// #8638
|
||||
test('checkbox click with v-model array value', async () => {
|
||||
await page().evaluate(() => {
|
||||
const { createApp, ref } = (window as any).Vue
|
||||
createApp({
|
||||
template: `
|
||||
{{cls}}
|
||||
<input
|
||||
id="checkEl"
|
||||
type="checkbox"
|
||||
@click="change"
|
||||
v-model="inputModel"
|
||||
value="a"
|
||||
>
|
||||
`,
|
||||
setup() {
|
||||
const inputModel = ref([])
|
||||
const count = ref(0)
|
||||
const change = () => {
|
||||
count.value++
|
||||
}
|
||||
return {
|
||||
inputModel,
|
||||
change,
|
||||
cls: count,
|
||||
}
|
||||
},
|
||||
}).mount('#app')
|
||||
})
|
||||
|
||||
expect(await isChecked('#checkEl')).toBe(false)
|
||||
expect(await html('#app')).toMatchInlineSnapshot(
|
||||
`"0 <input id="checkEl" type="checkbox" value="a">"`,
|
||||
)
|
||||
|
||||
await click('#checkEl')
|
||||
await nextTick()
|
||||
expect(await isChecked('#checkEl')).toBe(true)
|
||||
expect(await html('#app')).toMatchInlineSnapshot(
|
||||
`"1 <input id="checkEl" type="checkbox" value="a">"`,
|
||||
)
|
||||
|
||||
await click('#checkEl')
|
||||
await nextTick()
|
||||
expect(await isChecked('#checkEl')).toBe(false)
|
||||
expect(await html('#app')).toMatchInlineSnapshot(
|
||||
`"2 <input id="checkEl" type="checkbox" value="a">"`,
|
||||
)
|
||||
})
|
||||
|
||||
// #8579
|
||||
test('select click with v-model', async () => {
|
||||
await page().evaluate(() => {
|
||||
const { createApp } = (window as any).Vue
|
||||
createApp({
|
||||
template: `
|
||||
<p>
|
||||
Changed: {{changed}}
|
||||
</p>
|
||||
<p>
|
||||
Chosen: {{chosen}}
|
||||
</p>
|
||||
<form @input="changed = true">
|
||||
<select id="selectEl" v-model="chosen">
|
||||
<option v-for="choice of choices" :key="choice" :value="choice">{{ choice }}</option>
|
||||
</select>
|
||||
</form>
|
||||
`,
|
||||
data() {
|
||||
return {
|
||||
choices: ['A', 'B'],
|
||||
chosen: 'A',
|
||||
changed: false,
|
||||
}
|
||||
},
|
||||
}).mount('#app')
|
||||
})
|
||||
|
||||
expect(await value('#selectEl')).toBe('A')
|
||||
expect(await html('#app')).toMatchInlineSnapshot(
|
||||
`"<p> Changed: false</p><p> Chosen: A</p><form><select id="selectEl"><option value="A">A</option><option value="B">B</option></select></form>"`,
|
||||
)
|
||||
|
||||
await page().select('#selectEl', 'B')
|
||||
await nextTick()
|
||||
expect(await value('#selectEl')).toBe('B')
|
||||
expect(await html('#app')).toMatchInlineSnapshot(
|
||||
`"<p> Changed: true</p><p> Chosen: B</p><form><select id="selectEl"><option value="A">A</option><option value="B">B</option></select></form>"`,
|
||||
)
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue