Skip to content

Commit

Permalink
fix(defineModel): ensure trigger effect when prop changed (#9841)
Browse files Browse the repository at this point in the history
close #9838
  • Loading branch information
edison1105 committed Dec 16, 2023
1 parent 4070502 commit eb12f21
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 17 deletions.
86 changes: 85 additions & 1 deletion packages/runtime-core/__tests__/apiSetupHelpers.spec.ts
Expand Up @@ -16,7 +16,13 @@ import {
nextTick,
ref,
Ref,
watch
watch,
openBlock,
createVNode,
createElementVNode,
createBlock,
createElementBlock,
Fragment
} from '@vue/runtime-test'
import {
defineEmits,
Expand Down Expand Up @@ -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', () => {
Expand Down
37 changes: 21 additions & 16 deletions packages/runtime-core/src/apiSetupHelpers.ts
Expand Up @@ -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 {
Expand Down

0 comments on commit eb12f21

Please sign in to comment.