fix(v-model): handle mutations of v-model bound array/sets

fix #4096
This commit is contained in:
Evan You 2021-07-15 12:14:19 -04:00
parent c23153d82e
commit 2937530bef
3 changed files with 15 additions and 6 deletions

View File

@ -390,12 +390,12 @@ export function createPathGetter(ctx: any, path: string) {
} }
} }
function traverse(value: unknown, seen: Set<unknown> = new Set()) { export function traverse(value: unknown, seen: Set<unknown> = new Set()) {
if ( if (!isObject(value) || (value as any)[ReactiveFlags.SKIP]) {
!isObject(value) || return value
seen.has(value) || }
(value as any)[ReactiveFlags.SKIP] seen = seen || new Set()
) { if (seen.has(value)) {
return value return value
} }
seen.add(value) seen.add(value)

View File

@ -20,6 +20,7 @@ import { callWithAsyncErrorHandling, ErrorCodes } from './errorHandling'
import { ComponentPublicInstance } from './componentPublicInstance' import { ComponentPublicInstance } from './componentPublicInstance'
import { mapCompatDirectiveHook } from './compat/customDirective' import { mapCompatDirectiveHook } from './compat/customDirective'
import { pauseTracking, resetTracking } from '@vue/reactivity' import { pauseTracking, resetTracking } from '@vue/reactivity'
import { traverse } from './apiWatch'
export interface DirectiveBinding<V = any> { export interface DirectiveBinding<V = any> {
instance: ComponentPublicInstance | null instance: ComponentPublicInstance | null
@ -51,6 +52,7 @@ export interface ObjectDirective<T = any, V = any> {
beforeUnmount?: DirectiveHook<T, null, V> beforeUnmount?: DirectiveHook<T, null, V>
unmounted?: DirectiveHook<T, null, V> unmounted?: DirectiveHook<T, null, V>
getSSRProps?: SSRDirectiveHook getSSRProps?: SSRDirectiveHook
deep?: boolean
} }
export type FunctionDirective<T = any, V = any> = DirectiveHook<T, any, V> export type FunctionDirective<T = any, V = any> = DirectiveHook<T, any, V>
@ -101,6 +103,9 @@ export function withDirectives<T extends VNode>(
updated: dir updated: dir
} as ObjectDirective } as ObjectDirective
} }
if (dir.deep) {
traverse(value)
}
bindings.push({ bindings.push({
dir, dir,
instance, instance,

View File

@ -99,6 +99,8 @@ export const vModelText: ModelDirective<
} }
export const vModelCheckbox: ModelDirective<HTMLInputElement> = { export const vModelCheckbox: ModelDirective<HTMLInputElement> = {
// #4096 array checkboxes need to be deep traversed
deep: true,
created(el, _, vnode) { created(el, _, vnode) {
el._assign = getModelAssigner(vnode) el._assign = getModelAssigner(vnode)
addEventListener(el, 'change', () => { addEventListener(el, 'change', () => {
@ -171,6 +173,8 @@ export const vModelRadio: ModelDirective<HTMLInputElement> = {
} }
export const vModelSelect: ModelDirective<HTMLSelectElement> = { export const vModelSelect: ModelDirective<HTMLSelectElement> = {
// <select multiple> value need to be deep traversed
deep: true,
created(el, { value, modifiers: { number } }, vnode) { created(el, { value, modifiers: { number } }, vnode) {
const isSetModel = isSet(value) const isSetModel = isSet(value)
addEventListener(el, 'change', () => { addEventListener(el, 'change', () => {