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,
ref,
Ref,
watch
watch,
openBlock,
createVNode,
createElementVNode,
createBlock,
createElementBlock,
Fragment
} from '@vue/runtime-test'
import {
defineEmits,
@ -429,6 +435,84 @@ describe('SFC <script setup> helpers', () => {
await nextTick()
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', () => {

View File

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