]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
dx(computed): warn incorrect use of getCurrentInstance inside computed
authorEvan You <yyx990803@gmail.com>
Mon, 8 Jan 2024 10:12:40 +0000 (18:12 +0800)
committerEvan You <yyx990803@gmail.com>
Mon, 8 Jan 2024 10:12:40 +0000 (18:12 +0800)
ref #9974
close #10001

packages/runtime-core/__tests__/apiComputed.spec.ts [new file with mode: 0644]
packages/runtime-core/src/apiComputed.ts
packages/runtime-core/src/component.ts

diff --git a/packages/runtime-core/__tests__/apiComputed.spec.ts b/packages/runtime-core/__tests__/apiComputed.spec.ts
new file mode 100644 (file)
index 0000000..4646692
--- /dev/null
@@ -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()
+  })
+})
index 97db0da453cd63bcb48979c49e81f6067c9ad864..d634196764f30c966e5f79f4612e73a24aa0b952 100644 (file)
@@ -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<unknown>,
+): ComputedGetter<unknown> {
+  return () => {
+    isInComputedGetter = true
+    try {
+      return getter()
+    } finally {
+      isInComputedGetter = false
+    }
+  }
+}
 
 export const computed: typeof _computed = (
-  getterOrOptions: any,
+  getterOrOptions: ComputedGetter<unknown> | WritableComputedOptions<unknown>,
   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)
 }
index 6595df8fd5547daa32b2ed25fe4e7e736e5375b6..c77a17ff80bfc5333d49bb8493bfee9be90be5da 100644 (file)
@@ -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<string, unknown>
 
@@ -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,