From e0428884b57ac834274045bd33841263aeae259e Mon Sep 17 00:00:00 2001 From: LiuSeen <91084928+liuseen-l@users.noreply.github.com> Date: Wed, 7 Aug 2024 22:10:01 +0800 Subject: [PATCH] fix(defineModel): detect changes respect custom getter and setter (#11543) fix: #11541 fix: #11526 close: #11527 --- .../__tests__/helpers/useModel.spec.ts | 92 +++++++++++++++++++ packages/runtime-core/src/helpers/useModel.ts | 5 +- 2 files changed, 95 insertions(+), 2 deletions(-) diff --git a/packages/runtime-core/__tests__/helpers/useModel.spec.ts b/packages/runtime-core/__tests__/helpers/useModel.spec.ts index 4c30de2f26a..3c724b0ba00 100644 --- a/packages/runtime-core/__tests__/helpers/useModel.spec.ts +++ b/packages/runtime-core/__tests__/helpers/useModel.spec.ts @@ -657,4 +657,96 @@ describe('useModel', () => { expect(setValue).toBeCalledTimes(2) expect(msg.value).toBe(defaultVal) }) + + // #11526 + test('custom getter', () => { + let changeChildMsg!: (val: boolean) => void + const getter = (value: boolean) => !value + + const Comp = defineComponent({ + props: ['msg'], + emits: ['update:msg'], + setup(props) { + const childMsg = useModel(props, 'msg', { + get: getter, + set: value => !value, + }) + changeChildMsg = (val: boolean) => (childMsg.value = val) + return () => { + return childMsg.value + } + }, + }) + + const defaultVal = false + const msg = ref(defaultVal) + const Parent = defineComponent({ + setup() { + return () => + h(Comp, { + msg: msg.value, + 'onUpdate:msg': val => { + msg.value = val + }, + }) + }, + }) + + const root = nodeOps.createElement('div') + render(h(Parent), root) + + changeChildMsg(!getter(msg.value)) + expect(msg.value).toBe(true) + + changeChildMsg(!getter(msg.value)) + expect(msg.value).toBe(false) + }) + + // #11541 + test('custom setter', () => { + let changeChildMsg!: (val: boolean) => void + + const Comp = defineComponent({ + props: ['msg'], + emits: ['update:msg'], + setup(props) { + const childMsg = useModel(props, 'msg', { + set: value => { + if (value === msg.value) { + return null + } else { + return value + } + }, + }) + changeChildMsg = (val: boolean) => (childMsg.value = val) + return () => { + return childMsg.value + } + }, + }) + + const defaultVal = false + const msg = ref(defaultVal) + const Parent = defineComponent({ + setup() { + return () => + h(Comp, { + msg: msg.value, + 'onUpdate:msg': val => { + msg.value = val + }, + }) + }, + }) + + const root = nodeOps.createElement('div') + render(h(Parent), root) + + changeChildMsg(true) + expect(msg.value).toBe(true) + + changeChildMsg(true) + expect(msg.value).toBe(null) + }) }) diff --git a/packages/runtime-core/src/helpers/useModel.ts b/packages/runtime-core/src/helpers/useModel.ts index 5bcd316931d..8180fb32c47 100644 --- a/packages/runtime-core/src/helpers/useModel.ts +++ b/packages/runtime-core/src/helpers/useModel.ts @@ -51,8 +51,9 @@ export function useModel( }, set(value) { + const emittedValue = options.set ? options.set(value) : value if ( - !hasChanged(value, localValue) && + !hasChanged(emittedValue, localValue) && !(prevSetValue !== EMPTY_OBJ && hasChanged(value, prevSetValue)) ) { return @@ -74,7 +75,7 @@ export function useModel( localValue = value trigger() } - const emittedValue = options.set ? options.set(value) : value + i.emit(`update:${name}`, emittedValue) // #10279: if the local value is converted via a setter but the value // emitted to parent was the same, the parent will not trigger any