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
This commit is contained in:
HcySunYang 2020-12-01 06:48:51 +08:00 committed by GitHub
parent cd92836223
commit 87581cd266
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 76 additions and 5 deletions

View File

@ -6,7 +6,8 @@ import {
vModelDynamic, vModelDynamic,
withDirectives, withDirectives,
VNode, VNode,
ref ref,
reactive
} from '@vue/runtime-dom' } from '@vue/runtime-dom'
const triggerEvent = (type: string, el: Element) => { const triggerEvent = (type: string, el: Element) => {
@ -433,6 +434,78 @@ describe('vModel', () => {
expect(bar.checked).toEqual(false) expect(bar.checked).toEqual(false)
}) })
it(`should support the reactive array in setup as a checkbox model`, async () => {
const value = reactive<string[]>([])
const component = defineComponent({
setup() {
return () => {
return [
withVModel(
h('input', {
type: 'checkbox',
class: 'foo',
value: 'foo',
'onUpdate:modelValue': setValue.bind(this)
}),
value
),
withVModel(
h('input', {
type: 'checkbox',
class: 'bar',
value: 'bar',
'onUpdate:modelValue': setValue.bind(this)
}),
value
)
]
}
}
})
render(h(component), root)
const foo = root.querySelector('.foo')
const bar = root.querySelector('.bar')
foo.checked = true
triggerEvent('change', foo)
await nextTick()
expect(value).toMatchObject(['foo'])
bar.checked = true
triggerEvent('change', bar)
await nextTick()
expect(value).toMatchObject(['foo', 'bar'])
bar.checked = false
triggerEvent('change', bar)
await nextTick()
expect(value).toMatchObject(['foo'])
foo.checked = false
triggerEvent('change', foo)
await nextTick()
expect(value).toMatchObject([])
value.length = 0
value.push('foo')
await nextTick()
expect(bar.checked).toEqual(false)
expect(foo.checked).toEqual(true)
value.length = 0
value.push('bar')
await nextTick()
expect(foo.checked).toEqual(false)
expect(bar.checked).toEqual(true)
value.length = 0
await nextTick()
expect(foo.checked).toEqual(false)
expect(bar.checked).toEqual(false)
})
it(`should support Set as a checkbox model`, async () => { it(`should support Set as a checkbox model`, async () => {
const component = defineComponent({ const component = defineComponent({
data() { data() {

View File

@ -111,11 +111,9 @@ export const vModelCheckbox: ModelDirective<HTMLInputElement> = {
const index = looseIndexOf(modelValue, elementValue) const index = looseIndexOf(modelValue, elementValue)
const found = index !== -1 const found = index !== -1
if (checked && !found) { if (checked && !found) {
assign(modelValue.concat(elementValue)) modelValue.push(elementValue)
} else if (!checked && found) { } else if (!checked && found) {
const filtered = [...modelValue] modelValue.splice(index, 1)
filtered.splice(index, 1)
assign(filtered)
} }
} else if (isSet(modelValue)) { } else if (isSet(modelValue)) {
if (checked) { if (checked) {