Skip to content

Commit eb12f21

Browse files
authored
fix(defineModel): ensure trigger effect when prop changed (#9841)
close #9838
1 parent 4070502 commit eb12f21

File tree

2 files changed

+106
-17
lines changed

2 files changed

+106
-17
lines changed

packages/runtime-core/__tests__/apiSetupHelpers.spec.ts

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,13 @@ import {
1616
nextTick,
1717
ref,
1818
Ref,
19-
watch
19+
watch,
20+
openBlock,
21+
createVNode,
22+
createElementVNode,
23+
createBlock,
24+
createElementBlock,
25+
Fragment
2026
} from '@vue/runtime-test'
2127
import {
2228
defineEmits,
@@ -429,6 +435,84 @@ describe('SFC <script setup> helpers', () => {
429435
await nextTick()
430436
expect(serializeInner(root)).toBe('2')
431437
})
438+
439+
// #9838
440+
test('pass modelValue to slot (optimized mode) ', async () => {
441+
let foo: any
442+
const update = () => {
443+
foo.value = 'bar'
444+
}
445+
446+
const Comp = {
447+
render(this: any) {
448+
return this.$slots.default()
449+
}
450+
}
451+
452+
const childRender = vi.fn()
453+
const slotRender = vi.fn()
454+
const Child = defineComponent({
455+
props: ['modelValue'],
456+
emits: ['update:modelValue'],
457+
setup(props) {
458+
foo = useModel(props, 'modelValue')
459+
return () => {
460+
childRender()
461+
return (
462+
openBlock(),
463+
createElementBlock(Fragment, null, [
464+
createVNode(Comp, null, {
465+
default: () => {
466+
slotRender()
467+
return createElementVNode('div', null, foo.value)
468+
},
469+
_: 1 /* STABLE */
470+
})
471+
])
472+
)
473+
}
474+
}
475+
})
476+
477+
const msg = ref('')
478+
const setValue = vi.fn(v => (msg.value = v))
479+
const root = nodeOps.createElement('div')
480+
createApp({
481+
render() {
482+
return (
483+
openBlock(),
484+
createBlock(
485+
Child,
486+
{
487+
modelValue: msg.value,
488+
'onUpdate:modelValue': setValue
489+
},
490+
null,
491+
8 /* PROPS */,
492+
['modelValue']
493+
)
494+
)
495+
}
496+
}).mount(root)
497+
498+
expect(foo.value).toBe('')
499+
expect(msg.value).toBe('')
500+
expect(setValue).not.toBeCalled()
501+
expect(childRender).toBeCalledTimes(1)
502+
expect(slotRender).toBeCalledTimes(1)
503+
expect(serializeInner(root)).toBe('<div></div>')
504+
505+
// update from child
506+
update()
507+
508+
await nextTick()
509+
expect(msg.value).toBe('bar')
510+
expect(foo.value).toBe('bar')
511+
expect(setValue).toBeCalledTimes(1)
512+
expect(childRender).toBeCalledTimes(2)
513+
expect(slotRender).toBeCalledTimes(2)
514+
expect(serializeInner(root)).toBe('<div>bar</div>')
515+
})
432516
})
433517

434518
test('createPropsRestProxy', () => {

packages/runtime-core/src/apiSetupHelpers.ts

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -364,25 +364,30 @@ export function useModel(props: Record<string, any>, name: string): Ref {
364364
return ref() as any
365365
}
366366

367-
let localValue: any
368-
watchSyncEffect(() => {
369-
localValue = props[name]
370-
})
371-
372-
return customRef((track, trigger) => ({
373-
get() {
374-
track()
375-
return localValue
376-
},
377-
set(value) {
378-
const rawProps = i.vnode!.props
379-
if (!(rawProps && name in rawProps) && hasChanged(value, localValue)) {
380-
localValue = value
367+
return customRef((track, trigger) => {
368+
let localValue: any
369+
watchSyncEffect(() => {
370+
const propValue = props[name]
371+
if (hasChanged(localValue, propValue)) {
372+
localValue = propValue
381373
trigger()
382374
}
383-
i.emit(`update:${name}`, value)
375+
})
376+
return {
377+
get() {
378+
track()
379+
return localValue
380+
},
381+
set(value) {
382+
const rawProps = i.vnode!.props
383+
if (!(rawProps && name in rawProps) && hasChanged(value, localValue)) {
384+
localValue = value
385+
trigger()
386+
}
387+
i.emit(`update:${name}`, value)
388+
}
384389
}
385-
}))
390+
})
386391
}
387392

388393
function getContext(): SetupContext {

0 commit comments

Comments
 (0)