diff --git a/packages/runtime-core/__tests__/apiWatch.spec.ts b/packages/runtime-core/__tests__/apiWatch.spec.ts index 6ac335eaa58..e25aca26776 100644 --- a/packages/runtime-core/__tests__/apiWatch.spec.ts +++ b/packages/runtime-core/__tests__/apiWatch.spec.ts @@ -16,7 +16,9 @@ import { serializeInner, TestElement, h, - createApp + createApp, + watchPostEffect, + watchSyncEffect } from '@vue/runtime-test' import { ITERATE_KEY, @@ -28,7 +30,6 @@ import { Ref, effectScope } from '@vue/reactivity' -import { watchPostEffect } from '../src/apiWatch' // reference: https://vue-composition-api-rfc.netlify.com/api.html#watch @@ -444,6 +445,48 @@ describe('api: watch', () => { expect(result2).toBe(true) }) + it('watchSyncEffect', async () => { + const count = ref(0) + const count2 = ref(0) + + let callCount = 0 + let result1 + let result2 + const assertion = jest.fn(count => { + callCount++ + // on mount, the watcher callback should be called before DOM render + // on update, should be called before the count is updated + const expectedDOM = callCount === 1 ? `` : `${count - 1}` + result1 = serializeInner(root) === expectedDOM + + // in a sync callback, state mutation on the next line should not have + // executed yet on the 2nd call, but will be on the 3rd call. + const expectedState = callCount < 3 ? 0 : 1 + result2 = count2.value === expectedState + }) + + const Comp = { + setup() { + watchSyncEffect(() => { + assertion(count.value) + }) + return () => count.value + } + } + const root = nodeOps.createElement('div') + render(h(Comp), root) + expect(assertion).toHaveBeenCalledTimes(1) + expect(result1).toBe(true) + expect(result2).toBe(true) + + count.value++ + count2.value++ + await nextTick() + expect(assertion).toHaveBeenCalledTimes(3) + expect(result1).toBe(true) + expect(result2).toBe(true) + }) + it('should not fire on component unmount w/ flush: post', async () => { const toggle = ref(true) const cb = jest.fn() diff --git a/packages/runtime-core/src/apiWatch.ts b/packages/runtime-core/src/apiWatch.ts index f89694df804..4a41485629c 100644 --- a/packages/runtime-core/src/apiWatch.ts +++ b/packages/runtime-core/src/apiWatch.ts @@ -96,6 +96,19 @@ export function watchPostEffect( ) } +export function watchSyncEffect( + effect: WatchEffect, + options?: DebuggerOptions +) { + return doWatch( + effect, + null, + (__DEV__ + ? Object.assign(options || {}, { flush: 'sync' }) + : { flush: 'sync' }) as WatchOptionsBase + ) +} + // initial value for watchers to trigger on undefined initial values const INITIAL_WATCHER_VALUE = {} diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts index 4ce2a82c42b..26e70dd9141 100644 --- a/packages/runtime-core/src/index.ts +++ b/packages/runtime-core/src/index.ts @@ -34,7 +34,12 @@ export { getCurrentScope, onScopeDispose } from '@vue/reactivity' -export { watch, watchEffect, watchPostEffect } from './apiWatch' +export { + watch, + watchEffect, + watchPostEffect, + watchSyncEffect +} from './apiWatch' export { onBeforeMount, onMounted,