fix(defineModel): ensure trigger effect when prop changed (#9841)

close #9838
This commit is contained in:
edison 2023-12-16 12:15:30 +08:00 committed by GitHub
parent 4070502bd0
commit eb12f211b8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 106 additions and 17 deletions

View File

@ -16,7 +16,13 @@ import {
nextTick, nextTick,
ref, ref,
Ref, Ref,
watch watch,
openBlock,
createVNode,
createElementVNode,
createBlock,
createElementBlock,
Fragment
} from '@vue/runtime-test' } from '@vue/runtime-test'
import { import {
defineEmits, defineEmits,
@ -429,6 +435,84 @@ describe('SFC <script setup> helpers', () => {
await nextTick() await nextTick()
expect(serializeInner(root)).toBe('2') expect(serializeInner(root)).toBe('2')
}) })
// #9838
test('pass modelValue to slot (optimized mode) ', async () => {
let foo: any
const update = () => {
foo.value = 'bar'
}
const Comp = {
render(this: any) {
return this.$slots.default()
}
}
const childRender = vi.fn()
const slotRender = vi.fn()
const Child = defineComponent({
props: ['modelValue'],
emits: ['update:modelValue'],
setup(props) {
foo = useModel(props, 'modelValue')
return () => {
childRender()
return (
openBlock(),
createElementBlock(Fragment, null, [
createVNode(Comp, null, {
default: () => {
slotRender()
return createElementVNode('div', null, foo.value)
},
_: 1 /* STABLE */
})
])
)
}
}
})
const msg = ref('')
const setValue = vi.fn(v => (msg.value = v))
const root = nodeOps.createElement('div')
createApp({
render() {
return (
openBlock(),
createBlock(
Child,
{
modelValue: msg.value,
'onUpdate:modelValue': setValue
},
null,
8 /* PROPS */,
['modelValue']
)
)
}
}).mount(root)
expect(foo.value).toBe('')
expect(msg.value).toBe('')
expect(setValue).not.toBeCalled()
expect(childRender).toBeCalledTimes(1)
expect(slotRender).toBeCalledTimes(1)
expect(serializeInner(root)).toBe('<div></div>')
// update from child
update()
await nextTick()
expect(msg.value).toBe('bar')
expect(foo.value).toBe('bar')
expect(setValue).toBeCalledTimes(1)
expect(childRender).toBeCalledTimes(2)
expect(slotRender).toBeCalledTimes(2)
expect(serializeInner(root)).toBe('<div>bar</div>')
})
}) })
test('createPropsRestProxy', () => { test('createPropsRestProxy', () => {

View File

@ -364,25 +364,30 @@ export function useModel(props: Record<string, any>, name: string): Ref {
return ref() as any return ref() as any
} }
let localValue: any return customRef((track, trigger) => {
watchSyncEffect(() => { let localValue: any
localValue = props[name] watchSyncEffect(() => {
}) const propValue = props[name]
if (hasChanged(localValue, propValue)) {
return customRef((track, trigger) => ({ localValue = propValue
get() {
track()
return localValue
},
set(value) {
const rawProps = i.vnode!.props
if (!(rawProps && name in rawProps) && hasChanged(value, localValue)) {
localValue = value
trigger() trigger()
} }
i.emit(`update:${name}`, value) })
return {
get() {
track()
return localValue
},
set(value) {
const rawProps = i.vnode!.props
if (!(rawProps && name in rawProps) && hasChanged(value, localValue)) {
localValue = value
trigger()
}
i.emit(`update:${name}`, value)
}
} }
})) })
} }
function getContext(): SetupContext { function getContext(): SetupContext {