diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 2d2b4d2a1be..683f8fa9e3c 100644 --- a/packages/reactivity/src/effect.ts +++ b/packages/reactivity/src/effect.ts @@ -17,6 +17,7 @@ export interface ReactiveEffect { raw: () => T deps: Array options: ReactiveEffectOptions + allowRecurse: boolean } export interface ReactiveEffectOptions { @@ -100,6 +101,7 @@ function createReactiveEffect( } } as ReactiveEffect effect.id = uid++ + effect.allowRecurse = !!options.allowRecurse effect._isEffect = true effect.active = true effect.raw = fn @@ -180,7 +182,7 @@ export function trigger( const add = (effectsToAdd: Set | undefined) => { if (effectsToAdd) { effectsToAdd.forEach(effect => { - if (effect !== activeEffect || effect.options.allowRecurse) { + if (effect !== activeEffect || effect.allowRecurse) { effects.add(effect) } }) diff --git a/packages/runtime-core/__tests__/rendererComponent.spec.ts b/packages/runtime-core/__tests__/rendererComponent.spec.ts index d178c21e82c..402a6732dda 100644 --- a/packages/runtime-core/__tests__/rendererComponent.spec.ts +++ b/packages/runtime-core/__tests__/rendererComponent.spec.ts @@ -8,7 +8,9 @@ import { VNode, provide, inject, - Ref + Ref, + watch, + SetupContext } from '@vue/runtime-test' describe('renderer: component', () => { @@ -139,23 +141,21 @@ describe('renderer: component', () => { }) // #2170 - test('should have access to instance’s “$el” property in watcher when setting instance data', async () => { + test('should have access to instance’s “$el” property in watcher when rendereing with watched prop', async () => { function returnThis(this: any) { return this } - const dataWatchSpy = jest.fn(returnThis) + const propWatchSpy = jest.fn(returnThis) let instance: any const Comp = { - data() { - return { - testData: undefined - } + props: { + testProp: String }, watch: { - testData() { + testProp() { // @ts-ignore - dataWatchSpy(this.$el) + propWatchSpy(this.$el) } }, @@ -170,50 +170,50 @@ describe('renderer: component', () => { const root = nodeOps.createElement('div') render(h(Comp), root) + await nextTick() + expect(propWatchSpy).not.toHaveBeenCalled() - expect(dataWatchSpy).not.toHaveBeenCalled() - instance.testData = 'data' - + render(h(Comp, { testProp: 'prop ' }), root) await nextTick() - expect(dataWatchSpy).toHaveBeenCalledWith(instance.$el) + expect(propWatchSpy).toHaveBeenCalledWith(instance.$el) }) - // #2170 - test('should have access to instance’s “$el” property in watcher when rendereing with watched prop', async () => { - function returnThis(this: any) { - return this - } - const propWatchSpy = jest.fn(returnThis) - let instance: any - const Comp = { - props: { - testProp: String - }, + // #2200 + test('component child updating parent state in pre-flush should trigger parent re-render', async () => { + const outer = ref(0) + const App = { + setup() { + const inner = ref(0) - watch: { - testProp() { - // @ts-ignore - propWatchSpy(this.$el) + return () => { + return [ + h('div', inner.value), + h(Child, { + value: outer.value, + onUpdate: (val: number) => (inner.value = val) + }) + ] } - }, + } + } - created() { - instance = this - }, + const Child = { + props: ['value'], + setup(props: any, { emit }: SetupContext) { + watch(() => props.value, (val: number) => emit('update', val)) - render() { - return h('div') + return () => { + return h('div', props.value) + } } } const root = nodeOps.createElement('div') + render(h(App), root) + expect(serializeInner(root)).toBe(`
0
0
`) - render(h(Comp), root) - await nextTick() - expect(propWatchSpy).not.toHaveBeenCalled() - - render(h(Comp, { testProp: 'prop ' }), root) + outer.value++ await nextTick() - expect(propWatchSpy).toHaveBeenCalledWith(instance.$el) + expect(serializeInner(root)).toBe(`
1
1
`) }) })