From 324e817ef8d00c091e5d1537789542241c876d66 Mon Sep 17 00:00:00 2001 From: Evan You Date: Mon, 8 Jan 2024 18:12:40 +0800 Subject: [PATCH] dx(computed): warn incorrect use of getCurrentInstance inside computed ref #9974 close #10001 --- .../__tests__/apiComputed.spec.ts | 44 +++++++++++++++++++ packages/runtime-core/src/apiComputed.ts | 38 ++++++++++++++-- packages/runtime-core/src/component.ts | 15 ++++++- 3 files changed, 92 insertions(+), 5 deletions(-) create mode 100644 packages/runtime-core/__tests__/apiComputed.spec.ts diff --git a/packages/runtime-core/__tests__/apiComputed.spec.ts b/packages/runtime-core/__tests__/apiComputed.spec.ts new file mode 100644 index 00000000000..46466922571 --- /dev/null +++ b/packages/runtime-core/__tests__/apiComputed.spec.ts @@ -0,0 +1,44 @@ +import { + computed, + getCurrentInstance, + h, + nodeOps, + render, +} from '@vue/runtime-test' + +describe('api: computed', () => { + test('should warn if getCurrentInstance is called inside computed getter', () => { + const Comp = { + setup() { + const c = computed(() => { + getCurrentInstance() + return 1 + }) + return () => c.value + }, + } + render(h(Comp), nodeOps.createElement('div')) + expect( + 'getCurrentInstance() called inside a computed getter', + ).toHaveBeenWarned() + }) + + test('should warn if getCurrentInstance is called inside computed getter (object syntax)', () => { + const Comp = { + setup() { + const c = computed({ + get: () => { + getCurrentInstance() + return 1 + }, + set: () => {}, + }) + return () => c.value + }, + } + render(h(Comp), nodeOps.createElement('div')) + expect( + 'getCurrentInstance() called inside a computed getter', + ).toHaveBeenWarned() + }) +}) diff --git a/packages/runtime-core/src/apiComputed.ts b/packages/runtime-core/src/apiComputed.ts index 97db0da453c..d634196764f 100644 --- a/packages/runtime-core/src/apiComputed.ts +++ b/packages/runtime-core/src/apiComputed.ts @@ -1,10 +1,42 @@ -import { computed as _computed } from '@vue/reactivity' +import { + type ComputedGetter, + type WritableComputedOptions, + computed as _computed, +} from '@vue/reactivity' import { isInSSRComponentSetup } from './component' +import { isFunction } from '@vue/shared' + +/** + * For dev warning only. + * Context: https://github.com/vuejs/core/discussions/9974 + */ +export let isInComputedGetter = false + +function wrapComputedGetter( + getter: ComputedGetter, +): ComputedGetter { + return () => { + isInComputedGetter = true + try { + return getter() + } finally { + isInComputedGetter = false + } + } +} export const computed: typeof _computed = ( - getterOrOptions: any, + getterOrOptions: ComputedGetter | WritableComputedOptions, debugOptions?: any, ) => { - // @ts-expect-error + if (__DEV__) { + if (isFunction(getterOrOptions)) { + getterOrOptions = wrapComputedGetter(getterOrOptions) + } else { + getterOrOptions.get = wrapComputedGetter(getterOrOptions.get) + } + } + + // @ts-expect-error the 3rd argument is hidden from public types return _computed(getterOrOptions, debugOptions, isInSSRComponentSetup) } diff --git a/packages/runtime-core/src/component.ts b/packages/runtime-core/src/component.ts index 6595df8fd55..c77a17ff80b 100644 --- a/packages/runtime-core/src/component.ts +++ b/packages/runtime-core/src/component.ts @@ -85,6 +85,7 @@ import { } from './compat/compatConfig' import type { SchedulerJob } from './scheduler' import type { LifecycleHooks } from './enums' +import { isInComputedGetter } from './apiComputed' export type Data = Record @@ -631,8 +632,18 @@ export function createComponentInstance( export let currentInstance: ComponentInternalInstance | null = null -export const getCurrentInstance: () => ComponentInternalInstance | null = () => - currentInstance || currentRenderingInstance +export const getCurrentInstance: () => ComponentInternalInstance | null = + () => { + if (__DEV__ && isInComputedGetter) { + warn( + `getCurrentInstance() called inside a computed getter. ` + + `This is incorrect usage as computed getters are not guaranteed ` + + `to be executed with an active component instance. If you are using ` + + `a composable inside a computed getter, move it ouside to the setup scope.`, + ) + } + return currentInstance || currentRenderingInstance + } let internalSetCurrentInstance: ( instance: ComponentInternalInstance | null,