Skip to content

Commit 2937530

Browse files
committed
fix(v-model): handle mutations of v-model bound array/sets
fix #4096
1 parent c23153d commit 2937530

File tree

3 files changed

+15
-6
lines changed

3 files changed

+15
-6
lines changed

packages/runtime-core/src/apiWatch.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -390,12 +390,12 @@ export function createPathGetter(ctx: any, path: string) {
390390
}
391391
}
392392

393-
function traverse(value: unknown, seen: Set<unknown> = new Set()) {
394-
if (
395-
!isObject(value) ||
396-
seen.has(value) ||
397-
(value as any)[ReactiveFlags.SKIP]
398-
) {
393+
export function traverse(value: unknown, seen: Set<unknown> = new Set()) {
394+
if (!isObject(value) || (value as any)[ReactiveFlags.SKIP]) {
395+
return value
396+
}
397+
seen = seen || new Set()
398+
if (seen.has(value)) {
399399
return value
400400
}
401401
seen.add(value)

packages/runtime-core/src/directives.ts

+5
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { callWithAsyncErrorHandling, ErrorCodes } from './errorHandling'
2020
import { ComponentPublicInstance } from './componentPublicInstance'
2121
import { mapCompatDirectiveHook } from './compat/customDirective'
2222
import { pauseTracking, resetTracking } from '@vue/reactivity'
23+
import { traverse } from './apiWatch'
2324

2425
export interface DirectiveBinding<V = any> {
2526
instance: ComponentPublicInstance | null
@@ -51,6 +52,7 @@ export interface ObjectDirective<T = any, V = any> {
5152
beforeUnmount?: DirectiveHook<T, null, V>
5253
unmounted?: DirectiveHook<T, null, V>
5354
getSSRProps?: SSRDirectiveHook
55+
deep?: boolean
5456
}
5557

5658
export type FunctionDirective<T = any, V = any> = DirectiveHook<T, any, V>
@@ -101,6 +103,9 @@ export function withDirectives<T extends VNode>(
101103
updated: dir
102104
} as ObjectDirective
103105
}
106+
if (dir.deep) {
107+
traverse(value)
108+
}
104109
bindings.push({
105110
dir,
106111
instance,

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

+4
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@ export const vModelText: ModelDirective<
9999
}
100100

101101
export const vModelCheckbox: ModelDirective<HTMLInputElement> = {
102+
// #4096 array checkboxes need to be deep traversed
103+
deep: true,
102104
created(el, _, vnode) {
103105
el._assign = getModelAssigner(vnode)
104106
addEventListener(el, 'change', () => {
@@ -171,6 +173,8 @@ export const vModelRadio: ModelDirective<HTMLInputElement> = {
171173
}
172174

173175
export const vModelSelect: ModelDirective<HTMLSelectElement> = {
176+
// <select multiple> value need to be deep traversed
177+
deep: true,
174178
created(el, { value, modifiers: { number } }, vnode) {
175179
const isSetModel = isSet(value)
176180
addEventListener(el, 'change', () => {

0 commit comments

Comments
 (0)