]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(v-model): mutate original array for v-model multi checkbox (#2663)
authorHcySunYang <HcySunYang@outlook.com>
Mon, 30 Nov 2020 22:48:51 +0000 (06:48 +0800)
committerGitHub <noreply@github.com>
Mon, 30 Nov 2020 22:48:51 +0000 (17:48 -0500)
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

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

index a1d729be48c11705fbcf30a574378c95a3013444..255f49c8ed48263ae72fccf4fd92f28e6827e323 100644 (file)
@@ -6,7 +6,8 @@ import {
   vModelDynamic,
   withDirectives,
   VNode,
-  ref
+  ref,
+  reactive
 } from '@vue/runtime-dom'
 
 const triggerEvent = (type: string, el: Element) => {
@@ -433,6 +434,78 @@ describe('vModel', () => {
     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 () => {
     const component = defineComponent({
       data() {
index 78a5b130f93063ae535b8b28578d1d5953dc4427..ed5252039d114648854fc901a7c1a3e022577c1d 100644 (file)
@@ -111,11 +111,9 @@ export const vModelCheckbox: ModelDirective<HTMLInputElement> = {
         const index = looseIndexOf(modelValue, elementValue)
         const found = index !== -1
         if (checked && !found) {
-          assign(modelValue.concat(elementValue))
+          modelValue.push(elementValue)
         } else if (!checked && found) {
-          const filtered = [...modelValue]
-          filtered.splice(index, 1)
-          assign(filtered)
+          modelValue.splice(index, 1)
         }
       } else if (isSet(modelValue)) {
         if (checked) {