Skip to content

Commit 87581cd

Browse files
authored
fix(v-model): mutate original array for v-model multi checkbox (#2663)
Note: this change will break non-deep `watch` on the `v-model` bound array since the array is no longer replaced. This can be considered part of the Array watch changes in v3 as detailed at https://v3.vuejs.org/guide/migration/watch.html This is unfortunate but unavoidable since the issue that it fixes is more important: `v-model` should definitely work with a non-ref reactive array. fix #2662
1 parent cd92836 commit 87581cd

File tree

2 files changed

+76
-5
lines changed

2 files changed

+76
-5
lines changed

packages/runtime-dom/__tests__/directives/vModel.spec.ts

+74-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import {
66
vModelDynamic,
77
withDirectives,
88
VNode,
9-
ref
9+
ref,
10+
reactive
1011
} from '@vue/runtime-dom'
1112

1213
const triggerEvent = (type: string, el: Element) => {
@@ -433,6 +434,78 @@ describe('vModel', () => {
433434
expect(bar.checked).toEqual(false)
434435
})
435436

437+
it(`should support the reactive array in setup as a checkbox model`, async () => {
438+
const value = reactive<string[]>([])
439+
440+
const component = defineComponent({
441+
setup() {
442+
return () => {
443+
return [
444+
withVModel(
445+
h('input', {
446+
type: 'checkbox',
447+
class: 'foo',
448+
value: 'foo',
449+
'onUpdate:modelValue': setValue.bind(this)
450+
}),
451+
value
452+
),
453+
withVModel(
454+
h('input', {
455+
type: 'checkbox',
456+
class: 'bar',
457+
value: 'bar',
458+
'onUpdate:modelValue': setValue.bind(this)
459+
}),
460+
value
461+
)
462+
]
463+
}
464+
}
465+
})
466+
render(h(component), root)
467+
468+
const foo = root.querySelector('.foo')
469+
const bar = root.querySelector('.bar')
470+
471+
foo.checked = true
472+
triggerEvent('change', foo)
473+
await nextTick()
474+
expect(value).toMatchObject(['foo'])
475+
476+
bar.checked = true
477+
triggerEvent('change', bar)
478+
await nextTick()
479+
expect(value).toMatchObject(['foo', 'bar'])
480+
481+
bar.checked = false
482+
triggerEvent('change', bar)
483+
await nextTick()
484+
expect(value).toMatchObject(['foo'])
485+
486+
foo.checked = false
487+
triggerEvent('change', foo)
488+
await nextTick()
489+
expect(value).toMatchObject([])
490+
491+
value.length = 0
492+
value.push('foo')
493+
await nextTick()
494+
expect(bar.checked).toEqual(false)
495+
expect(foo.checked).toEqual(true)
496+
497+
value.length = 0
498+
value.push('bar')
499+
await nextTick()
500+
expect(foo.checked).toEqual(false)
501+
expect(bar.checked).toEqual(true)
502+
503+
value.length = 0
504+
await nextTick()
505+
expect(foo.checked).toEqual(false)
506+
expect(bar.checked).toEqual(false)
507+
})
508+
436509
it(`should support Set as a checkbox model`, async () => {
437510
const component = defineComponent({
438511
data() {

packages/runtime-dom/src/directives/vModel.ts

+2-4
Original file line numberDiff line numberDiff line change
@@ -111,11 +111,9 @@ export const vModelCheckbox: ModelDirective<HTMLInputElement> = {
111111
const index = looseIndexOf(modelValue, elementValue)
112112
const found = index !== -1
113113
if (checked && !found) {
114-
assign(modelValue.concat(elementValue))
114+
modelValue.push(elementValue)
115115
} else if (!checked && found) {
116-
const filtered = [...modelValue]
117-
filtered.splice(index, 1)
118-
assign(filtered)
116+
modelValue.splice(index, 1)
119117
}
120118
} else if (isSet(modelValue)) {
121119
if (checked) {

0 commit comments

Comments
 (0)