]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(v-model): handle mutations of v-model bound array/sets
authorEvan You <yyx990803@gmail.com>
Thu, 15 Jul 2021 16:14:19 +0000 (12:14 -0400)
committerEvan You <yyx990803@gmail.com>
Thu, 15 Jul 2021 16:14:19 +0000 (12:14 -0400)
fix #4096

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

index 34c8deb1eaa3e9f5ea7ca7d0a552ce8cddaf6877..2abc4e1447ff585982986b7189eda537d9f17a7e 100644 (file)
@@ -390,12 +390,12 @@ export function createPathGetter(ctx: any, path: string) {
   }
 }
 
-function traverse(value: unknown, seen: Set<unknown> = new Set()) {
-  if (
-    !isObject(value) ||
-    seen.has(value) ||
-    (value as any)[ReactiveFlags.SKIP]
-  ) {
+export function traverse(value: unknown, seen: Set<unknown> = new Set()) {
+  if (!isObject(value) || (value as any)[ReactiveFlags.SKIP]) {
+    return value
+  }
+  seen = seen || new Set()
+  if (seen.has(value)) {
     return value
   }
   seen.add(value)
index a82a9c7dec5d9855b1a86f87d5a0f495deae730c..58e56df0522d16c524f1b768da259d450ff6238a 100644 (file)
@@ -20,6 +20,7 @@ import { callWithAsyncErrorHandling, ErrorCodes } from './errorHandling'
 import { ComponentPublicInstance } from './componentPublicInstance'
 import { mapCompatDirectiveHook } from './compat/customDirective'
 import { pauseTracking, resetTracking } from '@vue/reactivity'
+import { traverse } from './apiWatch'
 
 export interface DirectiveBinding<V = any> {
   instance: ComponentPublicInstance | null
@@ -51,6 +52,7 @@ export interface ObjectDirective<T = any, V = any> {
   beforeUnmount?: DirectiveHook<T, null, V>
   unmounted?: DirectiveHook<T, null, V>
   getSSRProps?: SSRDirectiveHook
+  deep?: boolean
 }
 
 export type FunctionDirective<T = any, V = any> = DirectiveHook<T, any, V>
@@ -101,6 +103,9 @@ export function withDirectives<T extends VNode>(
         updated: dir
       } as ObjectDirective
     }
+    if (dir.deep) {
+      traverse(value)
+    }
     bindings.push({
       dir,
       instance,
index f63f909b3c638694fb091e8d329f4331bdc86035..d721e2f3715faaf22682454bacf8f760bfb79c2e 100644 (file)
@@ -99,6 +99,8 @@ export const vModelText: ModelDirective<
 }
 
 export const vModelCheckbox: ModelDirective<HTMLInputElement> = {
+  // #4096 array checkboxes need to be deep traversed
+  deep: true,
   created(el, _, vnode) {
     el._assign = getModelAssigner(vnode)
     addEventListener(el, 'change', () => {
@@ -171,6 +173,8 @@ export const vModelRadio: ModelDirective<HTMLInputElement> = {
 }
 
 export const vModelSelect: ModelDirective<HTMLSelectElement> = {
+  // <select multiple> value need to be deep traversed
+  deep: true,
   created(el, { value, modifiers: { number } }, vnode) {
     const isSetModel = isSet(value)
     addEventListener(el, 'change', () => {