]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat(types): provide ComponentInstance type (#5408)
authorCarlos Rodrigues <carlos@hypermob.co.uk>
Fri, 8 Dec 2023 14:24:38 +0000 (14:24 +0000)
committerGitHub <noreply@github.com>
Fri, 8 Dec 2023 14:24:38 +0000 (22:24 +0800)
packages/dts-test/componentInstance.test-d.tsx [new file with mode: 0644]
packages/runtime-core/src/component.ts
packages/runtime-core/src/index.ts

diff --git a/packages/dts-test/componentInstance.test-d.tsx b/packages/dts-test/componentInstance.test-d.tsx
new file mode 100644 (file)
index 0000000..2fb2031
--- /dev/null
@@ -0,0 +1,139 @@
+import {
+  defineComponent,
+  FunctionalComponent,
+  ComponentPublicInstance,
+  ComponentInstance,
+  ref
+} from 'vue'
+import { expectType, describe } from './utils'
+
+describe('defineComponent', () => {
+  const CompSetup = defineComponent({
+    props: {
+      test: String
+    },
+    setup() {
+      return {
+        a: 1
+      }
+    }
+  })
+  const compSetup: ComponentInstance<typeof CompSetup> = {} as any
+
+  expectType<string | undefined>(compSetup.test)
+  expectType<number>(compSetup.a)
+  expectType<ComponentPublicInstance>(compSetup)
+})
+describe('functional component', () => {
+  // Functional
+  const CompFunctional: FunctionalComponent<{ test?: string }> = {} as any
+  const compFunctional: ComponentInstance<typeof CompFunctional> = {} as any
+
+  expectType<string | undefined>(compFunctional.test)
+  expectType<ComponentPublicInstance>(compFunctional)
+
+  const CompFunction: (props: { test?: string }) => any = {} as any
+  const compFunction: ComponentInstance<typeof CompFunction> = {} as any
+
+  expectType<string | undefined>(compFunction.test)
+  expectType<ComponentPublicInstance>(compFunction)
+})
+
+describe('options component', () => {
+  // Options
+  const CompOptions = defineComponent({
+    props: {
+      test: String
+    },
+    data() {
+      return {
+        a: 1
+      }
+    },
+    computed: {
+      b() {
+        return 'test'
+      }
+    },
+    methods: {
+      func(a: string) {
+        return true
+      }
+    }
+  })
+  const compOptions: ComponentInstance<typeof CompOptions> = {} as any
+  expectType<string | undefined>(compOptions.test)
+  expectType<number>(compOptions.a)
+  expectType<(a: string) => boolean>(compOptions.func)
+  expectType<ComponentPublicInstance>(compOptions)
+})
+
+describe('object no defineComponent', () => {
+  // object - no defineComponent
+
+  const CompObjectSetup = {
+    props: {
+      test: String
+    },
+    setup() {
+      return {
+        a: 1
+      }
+    }
+  }
+  const compObjectSetup: ComponentInstance<typeof CompObjectSetup> = {} as any
+  expectType<string | undefined>(compObjectSetup.test)
+  expectType<number>(compObjectSetup.a)
+  expectType<ComponentPublicInstance>(compObjectSetup)
+
+  const CompObjectData = {
+    props: {
+      test: String
+    },
+    data() {
+      return {
+        a: 1
+      }
+    }
+  }
+  const compObjectData: ComponentInstance<typeof CompObjectData> = {} as any
+  expectType<string | undefined>(compObjectData.test)
+  expectType<number>(compObjectData.a)
+  expectType<ComponentPublicInstance>(compObjectData)
+
+  const CompObjectNoProps = {
+    data() {
+      return {
+        a: 1
+      }
+    }
+  }
+  const compObjectNoProps: ComponentInstance<typeof CompObjectNoProps> =
+    {} as any
+  expectType<string | undefined>(compObjectNoProps.test)
+  expectType<number>(compObjectNoProps.a)
+  expectType<ComponentPublicInstance>(compObjectNoProps)
+})
+
+describe('Generic component', () => {
+  const Comp = defineComponent(
+    // TODO: babel plugin to auto infer runtime props options from type
+    // similar to defineProps<{...}>()
+    <T extends string | number>(props: { msg: T; list: T[] }) => {
+      // use Composition API here like in <script setup>
+      const count = ref(0)
+
+      return () => (
+        // return a render function (both JSX and h() works)
+        <div>
+          {props.msg} {count.value}
+        </div>
+      )
+    }
+  )
+
+  // defaults to known types since types are resolved on instantiation
+  const comp: ComponentInstance<typeof Comp> = {} as any
+  expectType<string | number>(comp.msg)
+  expectType<Array<string | number>>(comp.list)
+})
index 348228081ffe218757e65d0b90896d63bddb2ea4..287e03d6c6d0171e8b930b4d7d707d734e72f47b 100644 (file)
@@ -83,6 +83,39 @@ import { LifecycleHooks } from './enums'
 
 export type Data = Record<string, unknown>
 
+/**
+ * Public utility type for extracting the instance type of a component.
+ * Works with all valid component definition types. This is intended to replace
+ * the usage of `InstanceType<typeof Comp>` which only works for
+ * constructor-based component definition types.
+ *
+ * Exmaple:
+ * ```ts
+ * const MyComp = { ... }
+ * declare const instance: ComponentInstance<typeof MyComp>
+ * ```
+ */
+export type ComponentInstance<T> = T extends { new (): ComponentPublicInstance }
+  ? InstanceType<T>
+  : T extends FunctionalComponent<infer Props, infer Emits>
+  ? ComponentPublicInstance<Props, {}, {}, {}, {}, Emits>
+  : T extends Component<
+      infer Props,
+      infer RawBindings,
+      infer D,
+      infer C,
+      infer M
+    >
+  ? // NOTE we override Props/RawBindings/D to make sure is not `unknown`
+    ComponentPublicInstance<
+      unknown extends Props ? {} : Props,
+      unknown extends RawBindings ? {} : RawBindings,
+      unknown extends D ? {} : D,
+      C,
+      M
+    >
+  : never // not a vue Component
+
 /**
  * For extending allowed non-declared props on components in TSX
  */
index 67ee06fda4ee95a0fa53c04603b6abb3900193c5..4c36250683e3cd44ba1ef883f9c13d467c6fa1c6 100644 (file)
@@ -230,7 +230,8 @@ export type {
   ComponentInternalInstance,
   SetupContext,
   ComponentCustomProps,
-  AllowedComponentProps
+  AllowedComponentProps,
+  ComponentInstance
 } from './component'
 export type { DefineComponent, PublicProps } from './apiDefineComponent'
 export type {