mirror of https://github.com/vuejs/core.git
fix(defineModel): ensure trigger effect when prop changed (#9841)
close #9838
This commit is contained in:
parent
4070502bd0
commit
eb12f211b8
|
@ -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', () => {
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue