Selaa lähdekoodia

feat(types): define component improvements (#12612)

Evan You 3 vuotta sitten
vanhempi
commit
fb93c1be77

+ 2 - 1
src/core/util/next-tick.ts

@@ -86,7 +86,8 @@ if (typeof Promise !== 'undefined' && isNative(Promise)) {
 }
 
 export function nextTick(): Promise<void>
-export function nextTick(cb: (...args: any[]) => any, ctx?: object): void
+export function nextTick<T>(this: T, cb: (this: T, ...args: any[]) => any): void
+export function nextTick<T>(cb: (this: T, ...args: any[]) => any, ctx: T): void
 /**
  * @internal
  */

+ 6 - 0
types/common.d.ts

@@ -13,3 +13,9 @@ type Equal<Left, Right> =
   (<U>() => U extends Left ? 1 : 0) extends (<U>() => U extends Right ? 1 : 0) ? true : false;
 
 export type HasDefined<T> = Equal<T, unknown> extends true ? false : true
+
+// If the the type T accepts type "any", output type Y, otherwise output type N.
+// https://stackoverflow.com/questions/49927523/disallow-call-with-any/49928360#49928360
+export type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N
+
+export type LooseRequired<T> = { [P in string & keyof T]: T[P] }

+ 7 - 4
types/index.d.ts

@@ -30,7 +30,8 @@ export {
   VNode,
   VNodeComponentOptions,
   VNodeData,
-  VNodeDirective
+  VNodeDirective,
+  ComponentCustomProps
 } from './vnode'
 
 export * from './v3-manual-apis'
@@ -47,13 +48,15 @@ export {
   // v2 already has option with same name and it's for a single computed
   ComputedOptions as ComponentComputedOptions,
   MethodOptions as ComponentMethodOptions,
-  ComponentPropsOptions
+  ComponentPropsOptions,
+  ComponentCustomOptions
 } from './v3-component-options'
 export {
   ComponentInstance,
   ComponentPublicInstance,
-  ComponentRenderProxy
-} from './v3-component-proxy'
+  CreateComponentPublicInstance,
+  ComponentCustomProperties
+} from './v3-component-public-instance'
 export {
   // PropType,
   // PropOptions,

+ 10 - 2
types/jsx.d.ts

@@ -1313,7 +1313,12 @@ type NativeElements = {
   >
 }
 
-import { VNode, VNodeData } from './vnode'
+import {
+  VNode,
+  VNodeData,
+  ComponentCustomProps,
+  AllowedComponentProps
+} from './vnode'
 
 declare global {
   namespace JSX {
@@ -1329,7 +1334,10 @@ declare global {
       // @ts-ignore suppress ts:2374 = Duplicate string index signature.
       [name: string]: any
     }
-    interface IntrinsicAttributes extends ReservedProps {}
+    interface IntrinsicAttributes
+      extends ReservedProps,
+        AllowedComponentProps,
+        ComponentCustomProps {}
   }
 }
 

+ 7 - 1
types/options.d.ts

@@ -2,6 +2,7 @@ import { Vue, CreateElement, CombinedVueInstance } from './vue'
 import { VNode, VNodeData, VNodeDirective, NormalizedScopedSlot } from './vnode'
 import { SetupContext } from './v3-setup-context'
 import { DebuggerEvent } from './v3-generated'
+import { DefineComponent } from './v3-define-component'
 
 type Constructor = {
   new (...args: any[]): any
@@ -19,6 +20,7 @@ export type Component<
   | typeof Vue
   | FunctionalComponentOptions<Props>
   | ComponentOptions<never, Data, Methods, Computed, Props, SetupBindings>
+  | DefineComponent<any, any, any, any, any>
 
 type EsModule<T> = T | { default: T }
 
@@ -174,7 +176,10 @@ export interface ComponentOptions<
   el?: Element | string
   template?: string
   // hack is for functional component type inference, should not be used in user code
-  render?(createElement: CreateElement, hack: RenderContext<Props>): VNode
+  render?(
+    createElement: CreateElement,
+    hack: RenderContext<Props>
+  ): VNode | null | void
   renderError?(createElement: CreateElement, err: Error): VNode
   staticRenderFns?: ((createElement: CreateElement) => VNode)[]
 
@@ -198,6 +203,7 @@ export interface ComponentOptions<
     [key: string]:
       | Component<any, any, any, any>
       | AsyncComponent<any, any, any, any>
+      | DefineComponent<any, any, any, any, any, any, any, any, any, any>
   }
   transitions?: { [key: string]: object }
   filters?: { [key: string]: Function }

+ 1080 - 0
types/test/v3/define-component-test.tsx

@@ -0,0 +1,1080 @@
+import {
+  Component,
+  defineComponent,
+  PropType,
+  ref,
+  reactive,
+  ComponentPublicInstance
+} from '../../index'
+import { describe, test, expectType, expectError, IsUnion } from '../utils'
+
+defineComponent({
+  props: {
+    foo: Number
+  },
+  render() {
+    this.foo
+  }
+})
+
+describe('with object props', () => {
+  interface ExpectedProps {
+    a?: number | undefined
+    b: string
+    e?: Function
+    h: boolean
+    j: undefined | (() => string | undefined)
+    bb: string
+    bbb: string
+    bbbb: string | undefined
+    bbbbb: string | undefined
+    cc?: string[] | undefined
+    dd: { n: 1 }
+    ee?: () => string
+    ff?: (a: number, b: string) => { a: boolean }
+    ccc?: string[] | undefined
+    ddd: string[]
+    eee: () => { a: string }
+    fff: (a: number, b: string) => { a: boolean }
+    hhh: boolean
+    ggg: 'foo' | 'bar'
+    ffff: (a: number, b: string) => { a: boolean }
+    iii?: (() => string) | (() => number)
+    jjj: ((arg1: string) => string) | ((arg1: string, arg2: string) => string)
+    kkk?: any
+    validated?: string
+    date?: Date
+    l?: Date
+    ll?: Date | number
+    lll?: string | number
+  }
+
+  type GT = string & { __brand: unknown }
+
+  const props = {
+    a: Number,
+    // required should make property non-void
+    b: {
+      type: String,
+      required: true as true
+    },
+    e: Function,
+    h: Boolean,
+    j: Function as PropType<undefined | (() => string | undefined)>,
+    // default value should infer type and make it non-void
+    bb: {
+      default: 'hello'
+    },
+    bbb: {
+      // Note: default function value requires arrow syntax + explicit
+      // annotation
+      default: (props: any) => (props.bb as string) || 'foo'
+    },
+    bbbb: {
+      type: String,
+      default: undefined
+    },
+    bbbbb: {
+      type: String,
+      default: () => undefined
+    },
+    // explicit type casting
+    cc: Array as PropType<string[]>,
+    // required + type casting
+    dd: {
+      type: Object as PropType<{ n: 1 }>,
+      required: true as true
+    },
+    // return type
+    ee: Function as PropType<() => string>,
+    // arguments + object return
+    ff: Function as PropType<(a: number, b: string) => { a: boolean }>,
+    // explicit type casting with constructor
+    ccc: Array as () => string[],
+    // required + constructor type casting
+    ddd: {
+      type: Array as () => string[],
+      required: true as true
+    },
+    // required + object return
+    eee: {
+      type: Function as PropType<() => { a: string }>,
+      required: true as true
+    },
+    // required + arguments + object return
+    fff: {
+      type: Function as PropType<(a: number, b: string) => { a: boolean }>,
+      required: true as true
+    },
+    hhh: {
+      type: Boolean,
+      required: true as true
+    },
+    // default + type casting
+    ggg: {
+      type: String as PropType<'foo' | 'bar'>,
+      default: 'foo'
+    },
+    // default + function
+    ffff: {
+      type: Function as PropType<(a: number, b: string) => { a: boolean }>,
+      default: (a: number, b: string) => ({ a: a > +b })
+    },
+    // union + function with different return types
+    iii: Function as PropType<(() => string) | (() => number)>,
+    // union + function with different args & same return type
+    jjj: {
+      type: Function as PropType<
+        ((arg1: string) => string) | ((arg1: string, arg2: string) => string)
+      >,
+      required: true as true
+    },
+    kkk: null,
+    validated: {
+      type: String,
+      // validator requires explicit annotation
+      validator: (val: unknown) => val !== ''
+    },
+    date: Date,
+    l: [Date],
+    ll: [Date, Number],
+    lll: [String, Number]
+  }
+
+  const MyComponent = defineComponent({
+    props,
+    setup(props) {
+      // type assertion. See https://github.com/SamVerschueren/tsd
+      expectType<ExpectedProps['a']>(props.a)
+      expectType<ExpectedProps['b']>(props.b)
+      expectType<ExpectedProps['e']>(props.e)
+      expectType<ExpectedProps['h']>(props.h)
+      expectType<ExpectedProps['j']>(props.j)
+      expectType<ExpectedProps['bb']>(props.bb)
+      expectType<ExpectedProps['bbb']>(props.bbb)
+      expectType<ExpectedProps['bbbb']>(props.bbbb)
+      expectType<ExpectedProps['bbbbb']>(props.bbbbb)
+      expectType<ExpectedProps['cc']>(props.cc)
+      expectType<ExpectedProps['dd']>(props.dd)
+      expectType<ExpectedProps['ee']>(props.ee)
+      expectType<ExpectedProps['ff']>(props.ff)
+      expectType<ExpectedProps['ccc']>(props.ccc)
+      expectType<ExpectedProps['ddd']>(props.ddd)
+      expectType<ExpectedProps['eee']>(props.eee)
+      expectType<ExpectedProps['fff']>(props.fff)
+      expectType<ExpectedProps['hhh']>(props.hhh)
+      expectType<ExpectedProps['ggg']>(props.ggg)
+      expectType<ExpectedProps['ffff']>(props.ffff)
+      if (typeof props.iii !== 'function') {
+        expectType<undefined>(props.iii)
+      }
+      expectType<ExpectedProps['iii']>(props.iii)
+      expectType<IsUnion<typeof props.jjj>>(true)
+      expectType<ExpectedProps['jjj']>(props.jjj)
+      expectType<ExpectedProps['kkk']>(props.kkk)
+      expectType<ExpectedProps['validated']>(props.validated)
+      expectType<ExpectedProps['date']>(props.date)
+      expectType<ExpectedProps['l']>(props.l)
+      expectType<ExpectedProps['ll']>(props.ll)
+      expectType<ExpectedProps['lll']>(props.lll)
+
+      // @ts-expect-error props should be readonly
+      expectError((props.a = 1))
+
+      // setup context
+      return {
+        c: ref(1),
+        d: {
+          e: ref('hi')
+        },
+        f: reactive({
+          g: ref('hello' as GT)
+        })
+      }
+    },
+    provide() {
+      return {}
+    },
+    render() {
+      const props = this.$props
+      expectType<ExpectedProps['a']>(props.a)
+      expectType<ExpectedProps['b']>(props.b)
+      expectType<ExpectedProps['e']>(props.e)
+      expectType<ExpectedProps['h']>(props.h)
+      expectType<ExpectedProps['bb']>(props.bb)
+      expectType<ExpectedProps['cc']>(props.cc)
+      expectType<ExpectedProps['dd']>(props.dd)
+      expectType<ExpectedProps['ee']>(props.ee)
+      expectType<ExpectedProps['ff']>(props.ff)
+      expectType<ExpectedProps['ccc']>(props.ccc)
+      expectType<ExpectedProps['ddd']>(props.ddd)
+      expectType<ExpectedProps['eee']>(props.eee)
+      expectType<ExpectedProps['fff']>(props.fff)
+      expectType<ExpectedProps['hhh']>(props.hhh)
+      expectType<ExpectedProps['ggg']>(props.ggg)
+      if (typeof props.iii !== 'function') {
+        expectType<undefined>(props.iii)
+      }
+      expectType<ExpectedProps['iii']>(props.iii)
+      expectType<IsUnion<typeof props.jjj>>(true)
+      expectType<ExpectedProps['jjj']>(props.jjj)
+      expectType<ExpectedProps['kkk']>(props.kkk)
+
+      // @ts-expect-error props should be readonly
+      expectError((props.a = 1))
+
+      // should also expose declared props on `this`
+      expectType<ExpectedProps['a']>(this.a)
+      expectType<ExpectedProps['b']>(this.b)
+      expectType<ExpectedProps['e']>(this.e)
+      expectType<ExpectedProps['h']>(this.h)
+      expectType<ExpectedProps['bb']>(this.bb)
+      expectType<ExpectedProps['cc']>(this.cc)
+      expectType<ExpectedProps['dd']>(this.dd)
+      expectType<ExpectedProps['ee']>(this.ee)
+      expectType<ExpectedProps['ff']>(this.ff)
+      expectType<ExpectedProps['ccc']>(this.ccc)
+      expectType<ExpectedProps['ddd']>(this.ddd)
+      expectType<ExpectedProps['eee']>(this.eee)
+      expectType<ExpectedProps['fff']>(this.fff)
+      expectType<ExpectedProps['hhh']>(this.hhh)
+      expectType<ExpectedProps['ggg']>(this.ggg)
+      if (typeof this.iii !== 'function') {
+        expectType<undefined>(this.iii)
+      }
+      expectType<ExpectedProps['iii']>(this.iii)
+      const { jjj } = this
+      expectType<IsUnion<typeof jjj>>(true)
+      expectType<ExpectedProps['jjj']>(this.jjj)
+      expectType<ExpectedProps['kkk']>(this.kkk)
+
+      // @ts-expect-error props on `this` should be readonly
+      expectError((this.a = 1))
+
+      // assert setup context unwrapping
+      expectType<number>(this.c)
+      expectType<string>(this.d.e.value)
+      expectType<GT>(this.f.g)
+
+      // setup context properties should be mutable
+      this.c = 2
+
+      return null
+    }
+  })
+
+  expectType<Component>(MyComponent)
+
+  // Test TSX
+  expectType<JSX.Element>(
+    <MyComponent
+      a={1}
+      b="b"
+      bb="bb"
+      e={() => {}}
+      cc={['cc']}
+      dd={{ n: 1 }}
+      ee={() => 'ee'}
+      ccc={['ccc']}
+      ddd={['ddd']}
+      eee={() => ({ a: 'eee' })}
+      fff={(a, b) => ({ a: a > +b })}
+      hhh={false}
+      ggg="foo"
+      jjj={() => ''}
+      // should allow class/style as attrs
+      class="bar"
+      style={{ color: 'red' }}
+      // // should allow key
+      key={'foo'}
+      // // should allow ref
+      ref={'foo'}
+    />
+  )
+
+  // @ts-expect-error missing required props
+  expectError(<MyComponent />)
+  expectError(
+    // @ts-expect-error wrong prop types
+    <MyComponent a={'wrong type'} b="foo" dd={{ n: 1 }} ddd={['foo']} />
+  )
+  // @ts-expect-error wrong prop types
+  expectError(<MyComponent ggg="baz" />)
+  // @ts-expect-error
+  expectError(<MyComponent b="foo" dd={{ n: 'string' }} ddd={['foo']} />)
+})
+
+// describe('type inference w/ optional props declaration', () => {
+//   const MyComponent = defineComponent<{ a: string[]; msg: string }>({
+//     setup(props) {
+//       expectType<string>(props.msg)
+//       expectType<string[]>(props.a)
+//       return {
+//         b: 1
+//       }
+//     }
+//   })
+
+//   expectType<JSX.Element>(<MyComponent msg="1" a={['1']} />)
+//   // @ts-expect-error
+//   expectError(<MyComponent />)
+//   // @ts-expect-error
+//   expectError(<MyComponent msg="1" />)
+// })
+
+// describe('type inference w/ direct setup function', () => {
+//   const MyComponent = defineComponent((_props: { msg: string }) => {})
+//   expectType<JSX.Element>(<MyComponent msg="foo" />)
+//   // @ts-expect-error
+//   expectError(<MyComponent />)
+//   expectError(<MyComponent msg="1" />)
+// })
+
+describe('type inference w/ array props declaration', () => {
+  const MyComponent = defineComponent({
+    props: ['a', 'b'],
+    setup(props) {
+      // @ts-expect-error props should be readonly
+      expectError((props.a = 1))
+      expectType<any>(props.a)
+      expectType<any>(props.b)
+      return {
+        c: 1
+      }
+    },
+    render() {
+      expectType<any>(this.$props.a)
+      expectType<any>(this.$props.b)
+      // @ts-expect-error
+      expectError((this.$props.a = 1))
+      expectType<any>(this.a)
+      expectType<any>(this.b)
+      expectType<number>(this.c)
+    }
+  })
+  expectType<JSX.Element>(<MyComponent a={[1, 2]} b="b" />)
+  // @ts-expect-error
+  expectError(<MyComponent other="other" />)
+})
+
+describe('type inference w/ options API', () => {
+  defineComponent({
+    props: { a: Number },
+    setup() {
+      return {
+        b: 123
+      }
+    },
+    data() {
+      // Limitation: we cannot expose the return result of setup() on `this`
+      // here in data() - somehow that would mess up the inference
+      expectType<number | undefined>(this.a)
+      return {
+        c: this.a || 123,
+        someRef: ref(0)
+      }
+    },
+    computed: {
+      d() {
+        expectType<number>(this.b)
+        return this.b + 1
+      },
+      e: {
+        get() {
+          expectType<number>(this.b)
+          expectType<number>(this.d)
+
+          return this.b + this.d
+        },
+        set(v: number) {
+          expectType<number>(this.b)
+          expectType<number>(this.d)
+          expectType<number>(v)
+        }
+      }
+    },
+    watch: {
+      a() {
+        expectType<number>(this.b)
+        this.b + 1
+      }
+    },
+    created() {
+      // props
+      expectType<number | undefined>(this.a)
+      // returned from setup()
+      expectType<number>(this.b)
+      // returned from data()
+      expectType<number>(this.c)
+      // computed
+      expectType<number>(this.d)
+      // computed get/set
+      expectType<number>(this.e)
+      // expectType<number>(this.someRef)
+    },
+    methods: {
+      doSomething() {
+        // props
+        expectType<number | undefined>(this.a)
+        // returned from setup()
+        expectType<number>(this.b)
+        // returned from data()
+        expectType<number>(this.c)
+        // computed
+        expectType<number>(this.d)
+        // computed get/set
+        expectType<number>(this.e)
+      },
+      returnSomething() {
+        return this.a
+      }
+    },
+    render() {
+      // props
+      expectType<number | undefined>(this.a)
+      // returned from setup()
+      expectType<number>(this.b)
+      // returned from data()
+      expectType<number>(this.c)
+      // computed
+      expectType<number>(this.d)
+      // computed get/set
+      expectType<number>(this.e)
+      // method
+      expectType<() => number | undefined>(this.returnSomething)
+    }
+  })
+})
+
+describe('with mixins', () => {
+  const MixinA = defineComponent({
+    emits: ['bar'],
+    props: {
+      aP1: {
+        type: String,
+        default: 'aP1'
+      },
+      aP2: Boolean
+    },
+    data() {
+      return {
+        a: 1
+      }
+    }
+  })
+  const MixinB = defineComponent({
+    props: ['bP1', 'bP2'],
+    data() {
+      return {
+        b: 2
+      }
+    }
+  })
+  const MixinC = defineComponent({
+    data() {
+      return {
+        c: 3
+      }
+    }
+  })
+  const MixinD = defineComponent({
+    mixins: [MixinA],
+    data() {
+      //@ts-expect-error computed are not available on data()
+      expectError<number>(this.dC1)
+      //@ts-expect-error computed are not available on data()
+      expectError<string>(this.dC2)
+
+      return {
+        d: 4
+      }
+    },
+    setup(props) {
+      expectType<string>(props.aP1)
+    },
+    computed: {
+      dC1() {
+        return this.d + this.a
+      },
+      dC2() {
+        return this.aP1 + 'dC2'
+      }
+    }
+  })
+  const MyComponent = defineComponent({
+    mixins: [MixinA, MixinB, MixinC, MixinD],
+    emits: ['click'],
+    props: {
+      // required should make property non-void
+      z: {
+        type: String,
+        required: true
+      }
+    },
+
+    data(vm) {
+      expectType<number>(vm.a)
+      expectType<number>(vm.b)
+      expectType<number>(vm.c)
+      expectType<number>(vm.d)
+
+      // should also expose declared props on `this`
+      expectType<number>(this.a)
+      expectType<string>(this.aP1)
+      expectType<boolean | undefined>(this.aP2)
+      expectType<number>(this.b)
+      expectType<any>(this.bP1)
+      expectType<number>(this.c)
+      expectType<number>(this.d)
+
+      return {}
+    },
+
+    setup(props) {
+      expectType<string>(props.z)
+      // props
+      // expectType<((...args: any[]) => any) | undefined>(props.onClick)
+      // from Base
+      // expectType<((...args: any[]) => any) | undefined>(props.onBar)
+      expectType<string>(props.aP1)
+      expectType<boolean | undefined>(props.aP2)
+      expectType<any>(props.bP1)
+      expectType<any>(props.bP2)
+      expectType<string>(props.z)
+    },
+    render() {
+      const props = this.$props
+      // props
+      // expectType<((...args: any[]) => any) | undefined>(props.onClick)
+      // from Base
+      // expectType<((...args: any[]) => any) | undefined>(props.onBar)
+      expectType<string>(props.aP1)
+      expectType<boolean | undefined>(props.aP2)
+      expectType<any>(props.bP1)
+      expectType<any>(props.bP2)
+      expectType<string>(props.z)
+
+      const data = this.$data
+      expectType<number>(data.a)
+      expectType<number>(data.b)
+      expectType<number>(data.c)
+      expectType<number>(data.d)
+
+      // should also expose declared props on `this`
+      expectType<number>(this.a)
+      expectType<string>(this.aP1)
+      expectType<boolean | undefined>(this.aP2)
+      expectType<number>(this.b)
+      expectType<any>(this.bP1)
+      expectType<number>(this.c)
+      expectType<number>(this.d)
+      expectType<number>(this.dC1)
+      expectType<string>(this.dC2)
+
+      // props should be readonly
+      // @ts-expect-error
+      expectError((this.aP1 = 'new'))
+      // @ts-expect-error
+      expectError((this.z = 1))
+
+      // props on `this` should be readonly
+      // @ts-expect-error
+      expectError((this.bP1 = 1))
+
+      // string value can not assigned to number type value
+      // @ts-expect-error
+      expectError((this.c = '1'))
+
+      // setup context properties should be mutable
+      this.d = 5
+
+      return null
+    }
+  })
+
+  // Test TSX
+  expectType<JSX.Element>(
+    <MyComponent aP1={'aP'} aP2 bP1={1} bP2={[1, 2]} z={'z'} />
+  )
+
+  // missing required props
+  // @ts-expect-error
+  expectError(<MyComponent />)
+
+  // wrong prop types
+  // @ts-expect-error
+  expectError(<MyComponent aP1="ap" aP2={'wrong type'} bP1="b" z={'z'} />)
+  // @ts-expect-error
+  expectError(<MyComponent aP1={1} bP2={[1]} />)
+})
+
+describe('with extends', () => {
+  const Base = defineComponent({
+    props: {
+      aP1: Boolean,
+      aP2: {
+        type: Number,
+        default: 2
+      }
+    },
+    data() {
+      return {
+        a: 1
+      }
+    },
+    computed: {
+      c(): number {
+        return this.aP2 + this.a
+      }
+    }
+  })
+  const MyComponent = defineComponent({
+    extends: Base,
+    props: {
+      // required should make property non-void
+      z: {
+        type: String,
+        required: true
+      }
+    },
+    render() {
+      const props = this.$props
+      // props
+      expectType<boolean | undefined>(props.aP1)
+      expectType<number>(props.aP2)
+      expectType<string>(props.z)
+
+      const data = this.$data
+      expectType<number>(data.a)
+
+      // should also expose declared props on `this`
+      expectType<number>(this.a)
+      expectType<boolean | undefined>(this.aP1)
+      expectType<number>(this.aP2)
+
+      // setup context properties should be mutable
+      this.a = 5
+
+      return null
+    }
+  })
+
+  // Test TSX
+  expectType<JSX.Element>(<MyComponent aP2={3} aP1 z={'z'} />)
+
+  // missing required props
+  // @ts-expect-error
+  expectError(<MyComponent />)
+
+  // wrong prop types
+  // @ts-expect-error
+  expectError(<MyComponent aP2={'wrong type'} z={'z'} />)
+  // @ts-expect-error
+  expectError(<MyComponent aP1={3} />)
+})
+
+describe('extends with mixins', () => {
+  const Mixin = defineComponent({
+    emits: ['bar'],
+    props: {
+      mP1: {
+        type: String,
+        default: 'mP1'
+      },
+      mP2: Boolean,
+      mP3: {
+        type: Boolean,
+        required: true
+      }
+    },
+    data() {
+      return {
+        a: 1
+      }
+    }
+  })
+  const Base = defineComponent({
+    emits: ['foo'],
+    props: {
+      p1: Boolean,
+      p2: {
+        type: Number,
+        default: 2
+      },
+      p3: {
+        type: Boolean,
+        required: true
+      }
+    },
+    data() {
+      return {
+        b: 2
+      }
+    },
+    computed: {
+      c(): number {
+        return this.p2 + this.b
+      }
+    }
+  })
+  const MyComponent = defineComponent({
+    extends: Base,
+    mixins: [Mixin],
+    emits: ['click'],
+    props: {
+      // required should make property non-void
+      z: {
+        type: String,
+        required: true
+      }
+    },
+    render() {
+      const props = this.$props
+      // props
+      // expectType<((...args: any[]) => any) | undefined>(props.onClick)
+      // from Mixin
+      // expectType<((...args: any[]) => any) | undefined>(props.onBar)
+      // from Base
+      // expectType<((...args: any[]) => any) | undefined>(props.onFoo)
+      expectType<boolean | undefined>(props.p1)
+      expectType<number>(props.p2)
+      expectType<string>(props.z)
+      expectType<string>(props.mP1)
+      expectType<boolean | undefined>(props.mP2)
+
+      const data = this.$data
+      expectType<number>(data.a)
+      expectType<number>(data.b)
+
+      // should also expose declared props on `this`
+      expectType<number>(this.a)
+      expectType<number>(this.b)
+      expectType<boolean | undefined>(this.p1)
+      expectType<number>(this.p2)
+      expectType<string>(this.mP1)
+      expectType<boolean | undefined>(this.mP2)
+
+      // setup context properties should be mutable
+      this.a = 5
+
+      return null
+    }
+  })
+
+  // Test TSX
+  expectType<JSX.Element>(<MyComponent mP1="p1" mP2 mP3 p1 p2={1} p3 z={'z'} />)
+
+  // mP1, mP2, p1, and p2 have default value. these are not required
+  expectType<JSX.Element>(<MyComponent mP3 p3 z={'z'} />)
+
+  // missing required props
+  // @ts-expect-error
+  expectError(<MyComponent mP3 p3 /* z='z' */ />)
+  // missing required props from mixin
+  // @ts-expect-error
+  expectError(<MyComponent /* mP3 */ p3 z="z" />)
+  // missing required props from extends
+  // @ts-expect-error
+  expectError(<MyComponent mP3 /* p3 */ z="z" />)
+
+  // wrong prop types
+  // @ts-expect-error
+  expectError(<MyComponent p2={'wrong type'} z={'z'} />)
+  // @ts-expect-error
+  expectError(<MyComponent mP1={3} />)
+
+  // #3468
+  const CompWithD = defineComponent({
+    data() {
+      return { foo: 1 }
+    }
+  })
+  const CompWithC = defineComponent({
+    computed: {
+      foo() {
+        return 1
+      }
+    }
+  })
+  const CompWithM = defineComponent({ methods: { foo() {} } })
+  const CompEmpty = defineComponent({})
+
+  defineComponent({
+    mixins: [CompWithD, CompEmpty],
+    mounted() {
+      expectType<number>(this.foo)
+    }
+  })
+  defineComponent({
+    mixins: [CompWithC, CompEmpty],
+    mounted() {
+      expectType<number>(this.foo)
+    }
+  })
+  defineComponent({
+    mixins: [CompWithM, CompEmpty],
+    mounted() {
+      expectType<() => void>(this.foo)
+    }
+  })
+})
+
+describe('defineComponent', () => {
+  test('should accept components defined with defineComponent', () => {
+    const comp = defineComponent({})
+    defineComponent({
+      components: { comp }
+    })
+  })
+})
+
+describe('emits', () => {
+  // Note: for TSX inference, ideally we want to map emits to onXXX props,
+  // but that requires type-level string constant concatenation as suggested in
+  // https://github.com/Microsoft/TypeScript/issues/12754
+
+  // The workaround for TSX users is instead of using emits, declare onXXX props
+  // and call them instead. Since `v-on:click` compiles to an `onClick` prop,
+  // this would also support other users consuming the component in templates
+  // with `v-on` listeners.
+
+  // with object emits
+  defineComponent({
+    emits: {
+      click: (n: number) => typeof n === 'number',
+      input: (b: string) => b.length > 1
+    },
+    setup(props, { emit }) {
+      // expectType<((n: number) => boolean) | undefined>(props.onClick)
+      // expectType<((b: string) => boolean) | undefined>(props.onInput)
+      emit('click', 1)
+      emit('input', 'foo')
+      //  @ts-expect-error
+      expectError(emit('nope'))
+      //  @ts-expect-error
+      expectError(emit('click'))
+      //  @ts-expect-error
+      expectError(emit('click', 'foo'))
+      //  @ts-expect-error
+      expectError(emit('input'))
+      //  @ts-expect-error
+      expectError(emit('input', 1))
+    },
+    created() {
+      this.$emit('click', 1)
+      this.$emit('input', 'foo')
+      //  @ts-expect-error
+      expectError(this.$emit('nope'))
+      //  @ts-expect-error
+      expectError(this.$emit('click'))
+      //  @ts-expect-error
+      expectError(this.$emit('click', 'foo'))
+      //  @ts-expect-error
+      expectError(this.$emit('input'))
+      //  @ts-expect-error
+      expectError(this.$emit('input', 1))
+    },
+    mounted() {
+      // #3599
+      this.$nextTick(function () {
+        // this should be bound to this instance
+        this.$emit('click', 1)
+        this.$emit('input', 'foo')
+        //  @ts-expect-error
+        expectError(this.$emit('nope'))
+        //  @ts-expect-error
+        expectError(this.$emit('click'))
+        //  @ts-expect-error
+        expectError(this.$emit('click', 'foo'))
+        //  @ts-expect-error
+        expectError(this.$emit('input'))
+        //  @ts-expect-error
+        expectError(this.$emit('input', 1))
+      })
+    }
+  })
+
+  // with array emits
+  defineComponent({
+    emits: ['foo', 'bar'],
+    setup(props, { emit }) {
+      // expectType<((...args: any[]) => any) | undefined>(props.onFoo)
+      // expectType<((...args: any[]) => any) | undefined>(props.onBar)
+      emit('foo')
+      emit('foo', 123)
+      emit('bar')
+      //  @ts-expect-error
+      expectError(emit('nope'))
+    },
+    created() {
+      this.$emit('foo')
+      this.$emit('foo', 123)
+      this.$emit('bar')
+      //  @ts-expect-error
+      expectError(this.$emit('nope'))
+    }
+  })
+
+  // with tsx
+  const Component = defineComponent({
+    emits: {
+      click: (n: number) => typeof n === 'number'
+    },
+    setup(props, { emit }) {
+      // expectType<((n: number) => any) | undefined>(props.onClick)
+      emit('click', 1)
+      //  @ts-expect-error
+      expectError(emit('click'))
+      //  @ts-expect-error
+      expectError(emit('click', 'foo'))
+    }
+  })
+
+  // defineComponent({
+  //   render() {
+  //     return (
+  //       <Component
+  //         onClick={(n: number) => {
+  //           return n + 1
+  //         }}
+  //       />
+  //     )
+  //   }
+  // })
+
+  // without emits
+  defineComponent({
+    setup(props, { emit }) {
+      emit('test', 1)
+      emit('test')
+    }
+  })
+
+  // emit should be valid when ComponentPublicInstance is used.
+  const instance = {} as ComponentPublicInstance
+  instance.$emit('test', 1)
+  instance.$emit('test')
+
+  // `this` should be void inside of emits validators
+  defineComponent({
+    props: ['bar'],
+    emits: {
+      foo(): boolean {
+        // @ts-expect-error
+        return this.bar === 3
+      }
+    }
+  })
+})
+
+// describe('componentOptions setup should be `SetupContext`', () => {
+//   expectType<ComponentOptions['setup']>(
+//     {} as (props: Record<string, any>, ctx: SetupContext) => any
+//   )
+// })
+
+describe('extract instance type', () => {
+  const Base = defineComponent({
+    props: {
+      baseA: {
+        type: Number,
+        default: 1
+      }
+    }
+  })
+  const MixinA = defineComponent({
+    props: {
+      mA: {
+        type: String,
+        default: ''
+      }
+    }
+  })
+  const CompA = defineComponent({
+    extends: Base,
+    mixins: [MixinA],
+    props: {
+      a: {
+        type: Boolean,
+        default: false
+      },
+      b: {
+        type: String,
+        required: true
+      },
+      c: Number
+    }
+  })
+
+  const compA = {} as InstanceType<typeof CompA>
+
+  expectType<boolean>(compA.a)
+  expectType<string>(compA.b)
+  expectType<number | undefined>(compA.c)
+  // mixins
+  expectType<string>(compA.mA)
+  // extends
+  expectType<number>(compA.baseA)
+
+  //  @ts-expect-error
+  expectError((compA.a = true))
+  //  @ts-expect-error
+  expectError((compA.b = 'foo'))
+  //  @ts-expect-error
+  expectError((compA.c = 1))
+  //  @ts-expect-error
+  expectError((compA.mA = 'foo'))
+  //  @ts-expect-error
+  expectError((compA.baseA = 1))
+})
+
+// #5948
+describe('DefineComponent should infer correct types when assigning to Component', () => {
+  let component: Component
+  component = defineComponent({
+    setup(_, { attrs, slots }) {
+      // @ts-expect-error should not be any
+      expectType<[]>(attrs)
+      // @ts-expect-error should not be any
+      expectType<[]>(slots)
+    }
+  })
+  expectType<Component>(component)
+})
+
+// #5969
+describe('should allow to assign props', () => {
+  const Child = defineComponent({
+    props: {
+      bar: String
+    }
+  })
+
+  const Parent = defineComponent({
+    props: {
+      ...Child.props,
+      foo: String
+    }
+  })
+
+  const child = new Child()
+  expectType<JSX.Element>(<Parent {...child.$props} />)
+})
+
+// check if defineComponent can be exported
+export default {
+  // no props
+  b: defineComponent({
+    data() {
+      return {}
+    }
+  }),
+  c: defineComponent({
+    props: ['a']
+  }),
+  d: defineComponent({
+    props: {
+      a: Number
+    }
+  })
+}

+ 162 - 33
types/v3-component-options.d.ts

@@ -2,11 +2,33 @@ import { Vue } from './vue'
 import { VNode } from './vnode'
 import { ComponentOptions as Vue2ComponentOptions } from './options'
 import { EmitsOptions, SetupContext } from './v3-setup-context'
-import { Data } from './common'
-import { ComponentPropsOptions, ExtractPropTypes } from './v3-component-props'
-import { ComponentRenderProxy } from './v3-component-proxy'
+import { Data, LooseRequired, UnionToIntersection } from './common'
+import {
+  ComponentPropsOptions,
+  ExtractDefaultPropTypes,
+  ExtractPropTypes
+} from './v3-component-props'
+import { CreateComponentPublicInstance } from './v3-component-public-instance'
 export { ComponentPropsOptions } from './v3-component-props'
 
+/**
+ * Interface for declaring custom options.
+ *
+ * @example
+ * ```ts
+ * declare module 'vue' {
+ *   interface ComponentCustomOptions {
+ *     beforeRouteUpdate?(
+ *       to: Route,
+ *       from: Route,
+ *       next: () => void
+ *     ): void
+ *   }
+ * }
+ * ```
+ */
+export interface ComponentCustomOptions {}
+
 export type ComputedGetter<T> = (ctx?: any) => T
 export type ComputedSetter<T> = (v: T) => void
 
@@ -34,24 +56,76 @@ export type SetupFunction<
   ctx: SetupContext<Emits>
 ) => RawBindings | (() => VNode | null) | void
 
-interface ComponentOptionsBase<
+type ExtractOptionProp<T> = T extends ComponentOptionsBase<
+  infer P, // Props
+  any, // RawBindings
+  any, // D
+  any, // C
+  any, // M
+  any, // Mixin
+  any, // Extends
+  any, // EmitsOptions
+  any // Defaults
+>
+  ? unknown extends P
+    ? {}
+    : P
+  : {}
+
+export interface ComponentOptionsBase<
   Props,
-  D = Data,
-  C extends ComputedOptions = {},
-  M extends MethodOptions = {}
+  RawBindings,
+  D,
+  C extends ComputedOptions,
+  M extends MethodOptions,
+  Mixin extends ComponentOptionsMixin,
+  Extends extends ComponentOptionsMixin,
+  Emits extends EmitsOptions,
+  EmitNames extends string = string,
+  Defaults = {}
 > extends Omit<
-    Vue2ComponentOptions<Vue, D, M, C, Props>,
-    'data' | 'computed' | 'method' | 'setup' | 'props'
-  > {
-  // allow any custom options
-  [key: string]: any
-
+      Vue2ComponentOptions<Vue, D, M, C, Props>,
+      'data' | 'computed' | 'methods' | 'setup' | 'props' | 'mixins' | 'extends'
+    >,
+    ComponentCustomOptions {
   // rewrite options api types
-  data?: (this: Props & Vue, vm: Props) => D
+  data?: (
+    this: CreateComponentPublicInstance<Props, {}, {}, {}, M, Mixin, Extends>,
+    vm: CreateComponentPublicInstance<Props, {}, {}, {}, M, Mixin, Extends>
+  ) => D
   computed?: C
   methods?: M
+  mixins?: Mixin[]
+  extends?: Extends
+  emits?: (Emits | EmitNames[]) & ThisType<void>
+  setup?: SetupFunction<
+    Readonly<
+      LooseRequired<
+        Props &
+          UnionToIntersection<ExtractOptionProp<Mixin>> &
+          UnionToIntersection<ExtractOptionProp<Extends>>
+      >
+    >,
+    RawBindings,
+    Emits
+  >
+
+  __defaults?: Defaults
 }
 
+export type ComponentOptionsMixin = ComponentOptionsBase<
+  any,
+  any,
+  any,
+  any,
+  any,
+  any,
+  any,
+  any,
+  any,
+  any
+>
+
 export type ExtractComputedReturns<T extends any> = {
   [key in keyof T]: T[key] extends { get: (...args: any[]) => infer TReturn }
     ? TReturn
@@ -66,17 +140,36 @@ export type ComponentOptionsWithProps<
   D = Data,
   C extends ComputedOptions = {},
   M extends MethodOptions = {},
-  Mixin = {},
-  Extends = {},
+  Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
+  Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
   Emits extends EmitsOptions = {},
   EmitsNames extends string = string,
-  Props = ExtractPropTypes<PropsOptions>
-> = ComponentOptionsBase<Props, D, C, M> & {
+  Props = ExtractPropTypes<PropsOptions>,
+  Defaults = ExtractDefaultPropTypes<PropsOptions>
+> = ComponentOptionsBase<
+  Props,
+  RawBindings,
+  D,
+  C,
+  M,
+  Mixin,
+  Extends,
+  Emits,
+  EmitsNames,
+  Defaults
+> & {
   props?: PropsOptions
-  emits?: (Emits | EmitsNames[]) & ThisType<void>
-  setup?: SetupFunction<Props, RawBindings, Emits>
 } & ThisType<
-    ComponentRenderProxy<Props, RawBindings, D, C, M, Mixin, Extends, Emits>
+    CreateComponentPublicInstance<
+      Props,
+      RawBindings,
+      D,
+      C,
+      M,
+      Mixin,
+      Extends,
+      Emits
+    >
   >
 
 export type ComponentOptionsWithArrayProps<
@@ -85,17 +178,35 @@ export type ComponentOptionsWithArrayProps<
   D = Data,
   C extends ComputedOptions = {},
   M extends MethodOptions = {},
-  Mixin = {},
-  Extends = {},
+  Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
+  Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
   Emits extends EmitsOptions = {},
   EmitsNames extends string = string,
   Props = Readonly<{ [key in PropNames]?: any }>
-> = ComponentOptionsBase<Props, D, C, M> & {
+> = ComponentOptionsBase<
+  Props,
+  RawBindings,
+  D,
+  C,
+  M,
+  Mixin,
+  Extends,
+  Emits,
+  EmitsNames,
+  {}
+> & {
   props?: PropNames[]
-  emits?: (Emits | EmitsNames[]) & ThisType<void>
-  setup?: SetupFunction<Props, RawBindings, Emits>
 } & ThisType<
-    ComponentRenderProxy<Props, RawBindings, D, C, M, Mixin, Extends, Emits>
+    CreateComponentPublicInstance<
+      Props,
+      RawBindings,
+      D,
+      C,
+      M,
+      Mixin,
+      Extends,
+      Emits
+    >
   >
 
 export type ComponentOptionsWithoutProps<
@@ -104,16 +215,34 @@ export type ComponentOptionsWithoutProps<
   D = Data,
   C extends ComputedOptions = {},
   M extends MethodOptions = {},
-  Mixin = {},
-  Extends = {},
+  Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
+  Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
   Emits extends EmitsOptions = {},
   EmitsNames extends string = string
-> = ComponentOptionsBase<Props, D, C, M> & {
+> = ComponentOptionsBase<
+  Props,
+  RawBindings,
+  D,
+  C,
+  M,
+  Mixin,
+  Extends,
+  Emits,
+  EmitsNames,
+  {}
+> & {
   props?: undefined
-  emits?: (Emits | EmitsNames[]) & ThisType<void>
-  setup?: SetupFunction<Props, RawBindings, Emits>
 } & ThisType<
-    ComponentRenderProxy<Props, RawBindings, D, C, M, Mixin, Extends, Emits>
+    CreateComponentPublicInstance<
+      Props,
+      RawBindings,
+      D,
+      C,
+      M,
+      Mixin,
+      Extends,
+      Emits
+    >
   >
 
 export type WithLegacyAPI<T, D, C, M, Props> = T &

+ 19 - 20
types/v3-component-props.d.ts

@@ -1,4 +1,4 @@
-import { Data } from './common'
+import { Data, IfAny } from './common'
 
 export type ComponentPropsOptions<P = Data> =
   | ComponentObjectPropsOptions<P>
@@ -48,26 +48,25 @@ type ExtractCorrectPropType<T> = T extends Function
   ? ExtractFunctionPropType<T>
   : Exclude<T, Function>
 
-// prettier-ignore
-type InferPropType<T> = T extends null
+type InferPropType<T> = [T] extends [null]
   ? any // null & true would fail to infer
-  : T extends { type: null | true }
-    ? any // As TS issue https://github.com/Microsoft/TypeScript/issues/14829 // somehow `ObjectConstructor` when inferred from { (): T } becomes `any` // `BooleanConstructor` when inferred from PropConstructor(with PropMethod) becomes `Boolean`
-    : T extends ObjectConstructor | { type: ObjectConstructor }
-      ? Record<string, any>
-      : T extends BooleanConstructor | { type: BooleanConstructor }
-        ? boolean
-          : T extends DateConstructor | { type: DateConstructor}
-            ? Date
-              : T extends FunctionConstructor
-                ? Function
-                : T extends Prop<infer V, infer D>
-                  ? unknown extends V
-                    ? D extends null | undefined
-                      ? V
-                      : D
-                    : ExtractCorrectPropType<V>
-                  : T
+  : [T] extends [{ type: null | true }]
+  ? any // As TS issue https://github.com/Microsoft/TypeScript/issues/14829 // somehow `ObjectConstructor` when inferred from { (): T } becomes `any` // `BooleanConstructor` when inferred from PropConstructor(with PropMethod) becomes `Boolean`
+  : [T] extends [ObjectConstructor | { type: ObjectConstructor }]
+  ? Record<string, any>
+  : [T] extends [BooleanConstructor | { type: BooleanConstructor }]
+  ? boolean
+  : [T] extends [DateConstructor | { type: DateConstructor }]
+  ? Date
+  : [T] extends [(infer U)[] | { type: (infer U)[] }]
+  ? U extends DateConstructor
+    ? Date | InferPropType<U>
+    : InferPropType<U>
+  : [T] extends [Prop<infer V, infer D>]
+  ? unknown extends V
+    ? IfAny<V, V, D>
+    : V
+  : T
 
 export type ExtractPropTypes<O> = {
   // use `keyof Pick<O, RequiredKeys<O>>` instead of `RequiredKeys<O>` to support IDE features

+ 0 - 189
types/v3-component-proxy.d.ts

@@ -1,189 +0,0 @@
-import { ExtractDefaultPropTypes, ExtractPropTypes } from './v3-component-props'
-import {
-  nextTick,
-  ShallowUnwrapRef,
-  UnwrapNestedRefs,
-  WatchOptions,
-  WatchStopHandle
-} from './v3-generated'
-import { Data } from './common'
-
-import { Vue, VueConstructor } from './vue'
-import { ComponentOptions as Vue2ComponentOptions } from './options'
-import {
-  ComputedOptions,
-  MethodOptions,
-  ExtractComputedReturns
-} from './v3-component-options'
-import {
-  ComponentRenderEmitFn,
-  EmitFn,
-  EmitsOptions,
-  ObjectEmitsOptions,
-  Slots
-} from './v3-setup-context'
-
-type EmitsToProps<T extends EmitsOptions> = T extends string[]
-  ? {
-      [K in string & `on${Capitalize<T[number]>}`]?: (...args: any[]) => any
-    }
-  : T extends ObjectEmitsOptions
-  ? {
-      [K in string &
-        `on${Capitalize<string & keyof T>}`]?: K extends `on${infer C}`
-        ? T[Uncapitalize<C>] extends null
-          ? (...args: any[]) => any
-          : (
-              ...args: T[Uncapitalize<C>] extends (...args: infer P) => any
-                ? P
-                : never
-            ) => any
-        : never
-    }
-  : {}
-
-export type ComponentInstance = InstanceType<VueConstructor>
-
-// public properties exposed on the proxy, which is used as the render context
-// in templates (as `this` in the render option)
-export type ComponentRenderProxy<
-  P = {}, // props type extracted from props option
-  B = {}, // raw bindings returned from setup()
-  D = {}, // return from data()
-  C extends ComputedOptions = {},
-  M extends MethodOptions = {},
-  Mixin = {},
-  Extends = {},
-  Emits extends EmitsOptions = {},
-  PublicProps = P,
-  Defaults = {},
-  MakeDefaultsOptional extends boolean = false
-> = {
-  $data: D
-  $props: Readonly<
-    MakeDefaultsOptional extends true
-      ? Partial<Defaults> & Omit<P & PublicProps, keyof Defaults>
-      : P & PublicProps
-  >
-  $attrs: Record<string, string>
-  $emit: ComponentRenderEmitFn<
-    Emits,
-    keyof Emits,
-    ComponentRenderProxy<
-      P,
-      B,
-      D,
-      C,
-      M,
-      Mixin,
-      Extends,
-      Emits,
-      PublicProps,
-      Defaults,
-      MakeDefaultsOptional
-    >
-  >
-} & Readonly<P> &
-  ShallowUnwrapRef<B> &
-  D &
-  M &
-  ExtractComputedReturns<C> &
-  Omit<Vue, '$data' | '$props' | '$attrs' | '$emit'>
-
-// for Vetur and TSX support
-type VueConstructorProxy<
-  PropsOptions,
-  RawBindings,
-  Data,
-  Computed extends ComputedOptions,
-  Methods extends MethodOptions,
-  Mixin = {},
-  Extends = {},
-  Emits extends EmitsOptions = {},
-  Props = ExtractPropTypes<PropsOptions> &
-    ({} extends Emits ? {} : EmitsToProps<Emits>)
-> = Omit<VueConstructor, never> & {
-  new (...args: any[]): ComponentRenderProxy<
-    Props,
-    ShallowUnwrapRef<RawBindings>,
-    Data,
-    Computed,
-    Methods,
-    Mixin,
-    Extends,
-    Emits,
-    Props,
-    ExtractDefaultPropTypes<PropsOptions>,
-    true
-  >
-}
-
-type DefaultData<V> = object | ((this: V) => object)
-type DefaultMethods<V> = { [key: string]: (this: V, ...args: any[]) => any }
-type DefaultComputed = { [key: string]: any }
-
-export type VueProxy<
-  PropsOptions,
-  RawBindings,
-  Data = DefaultData<Vue>,
-  Computed extends ComputedOptions = DefaultComputed,
-  Methods extends MethodOptions = DefaultMethods<Vue>,
-  Mixin = {},
-  Extends = {},
-  Emits extends EmitsOptions = {}
-> = Vue2ComponentOptions<
-  Vue,
-  ShallowUnwrapRef<RawBindings> & Data,
-  Methods,
-  Computed,
-  PropsOptions,
-  ExtractPropTypes<PropsOptions>
-> &
-  VueConstructorProxy<
-    PropsOptions,
-    RawBindings,
-    Data,
-    Computed,
-    Methods,
-    Mixin,
-    Extends,
-    Emits
-  >
-
-// public properties exposed on the proxy, which is used as the render context
-// in templates (as `this` in the render option)
-export type ComponentPublicInstance<
-  P = {}, // props type extracted from props option
-  B = {}, // raw bindings returned from setup()
-  D = {}, // return from data()
-  C extends ComputedOptions = {},
-  M extends MethodOptions = {},
-  E extends EmitsOptions = {},
-  PublicProps = P,
-  Defaults = {},
-  MakeDefaultsOptional extends boolean = false
-> = {
-  $data: D
-  $props: MakeDefaultsOptional extends true
-    ? Partial<Defaults> & Omit<P & PublicProps, keyof Defaults>
-    : P & PublicProps
-  $attrs: Data
-  $refs: Data
-  $slots: Slots
-  $root: ComponentPublicInstance | null
-  $parent: ComponentPublicInstance | null
-  $emit: EmitFn<E>
-  $el: any
-  // $options: Options & MergedComponentOptionsOverride
-  $forceUpdate: () => void
-  $nextTick: typeof nextTick
-  $watch(
-    source: string | Function,
-    cb: Function,
-    options?: WatchOptions
-  ): WatchStopHandle
-} & P &
-  ShallowUnwrapRef<B> &
-  UnwrapNestedRefs<D> &
-  ExtractComputedReturns<C> &
-  M

+ 230 - 0
types/v3-component-public-instance.d.ts

@@ -0,0 +1,230 @@
+import { ExtractDefaultPropTypes, ExtractPropTypes } from './v3-component-props'
+import {
+  DebuggerEvent,
+  nextTick,
+  ShallowUnwrapRef,
+  UnwrapNestedRefs,
+  WatchOptions,
+  WatchStopHandle
+} from './v3-generated'
+import { Data, UnionToIntersection } from './common'
+
+import { VueConstructor } from './vue'
+import {
+  ComputedOptions,
+  MethodOptions,
+  ExtractComputedReturns,
+  ComponentOptionsMixin,
+  ComponentOptionsBase
+} from './v3-component-options'
+import { EmitFn, EmitsOptions, Slots } from './v3-setup-context'
+
+/**
+ * Custom properties added to component instances in any way and can be accessed through `this`
+ *
+ * @example
+ * ```ts
+ * import { Router } from 'vue-router'
+ *
+ * declare module 'vue' {
+ *   interface ComponentCustomProperties {
+ *     $router: Router
+ *   }
+ * }
+ * ```
+ */
+export interface ComponentCustomProperties {}
+
+export type ComponentInstance = InstanceType<VueConstructor>
+
+export type OptionTypesKeys = 'P' | 'B' | 'D' | 'C' | 'M' | 'Defaults'
+
+export type OptionTypesType<
+  P = {},
+  B = {},
+  D = {},
+  C extends ComputedOptions = {},
+  M extends MethodOptions = {},
+  Defaults = {}
+> = {
+  P: P
+  B: B
+  D: D
+  C: C
+  M: M
+  Defaults: Defaults
+}
+
+type IsDefaultMixinComponent<T> = T extends ComponentOptionsMixin
+  ? ComponentOptionsMixin extends T
+    ? true
+    : false
+  : false
+
+type MixinToOptionTypes<T> = T extends ComponentOptionsBase<
+  infer P,
+  infer B,
+  infer D,
+  infer C,
+  infer M,
+  infer Mixin,
+  infer Extends,
+  any,
+  any,
+  infer Defaults
+>
+  ? OptionTypesType<P & {}, B & {}, D & {}, C & {}, M & {}, Defaults & {}> &
+      IntersectionMixin<Mixin> &
+      IntersectionMixin<Extends>
+  : never
+
+// ExtractMixin(map type) is used to resolve circularly references
+type ExtractMixin<T> = {
+  Mixin: MixinToOptionTypes<T>
+}[T extends ComponentOptionsMixin ? 'Mixin' : never]
+
+type IntersectionMixin<T> = IsDefaultMixinComponent<T> extends true
+  ? OptionTypesType<{}, {}, {}, {}, {}, {}>
+  : UnionToIntersection<ExtractMixin<T>>
+
+type UnwrapMixinsType<
+  T,
+  Type extends OptionTypesKeys
+> = T extends OptionTypesType ? T[Type] : never
+
+type EnsureNonVoid<T> = T extends void ? {} : T
+
+export type CreateComponentPublicInstance<
+  P = {},
+  B = {},
+  D = {},
+  C extends ComputedOptions = {},
+  M extends MethodOptions = {},
+  Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
+  Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
+  E extends EmitsOptions = {},
+  PublicProps = P,
+  Defaults = {},
+  MakeDefaultsOptional extends boolean = false,
+  PublicMixin = IntersectionMixin<Mixin> & IntersectionMixin<Extends>,
+  PublicP = UnwrapMixinsType<PublicMixin, 'P'> & EnsureNonVoid<P>,
+  PublicB = UnwrapMixinsType<PublicMixin, 'B'> & EnsureNonVoid<B>,
+  PublicD = UnwrapMixinsType<PublicMixin, 'D'> & EnsureNonVoid<D>,
+  PublicC extends ComputedOptions = UnwrapMixinsType<PublicMixin, 'C'> &
+    EnsureNonVoid<C>,
+  PublicM extends MethodOptions = UnwrapMixinsType<PublicMixin, 'M'> &
+    EnsureNonVoid<M>,
+  PublicDefaults = UnwrapMixinsType<PublicMixin, 'Defaults'> &
+    EnsureNonVoid<Defaults>
+> = ComponentPublicInstance<
+  PublicP,
+  PublicB,
+  PublicD,
+  PublicC,
+  PublicM,
+  E,
+  PublicProps,
+  PublicDefaults,
+  MakeDefaultsOptional
+>
+
+// public properties exposed on the proxy, which is used as the render context
+// in templates (as `this` in the render option)
+export type ComponentPublicInstance<
+  P = {}, // props type extracted from props option
+  B = {}, // raw bindings returned from setup()
+  D = {}, // return from data()
+  C extends ComputedOptions = {},
+  M extends MethodOptions = {},
+  E extends EmitsOptions = {},
+  PublicProps = P,
+  Defaults = {},
+  MakeDefaultsOptional extends boolean = false,
+  Options = ComponentOptionsBase<
+    any,
+    any,
+    any,
+    any,
+    any,
+    any,
+    any,
+    any,
+    any,
+    any
+  >
+> = {
+  // $: ComponentInternalInstance
+  $data: D
+  $props: Readonly<
+    MakeDefaultsOptional extends true
+      ? Partial<Defaults> & Omit<P & PublicProps, keyof Defaults>
+      : P & PublicProps
+  >
+  $attrs: Data
+  $refs: Data
+  $slots: Slots
+  $root: ComponentPublicInstance | null
+  $parent: ComponentPublicInstance | null
+  $emit: EmitFn<E>
+  $el: any
+  $options: Options & MergedComponentOptionsOverride
+  $forceUpdate: () => void
+  $nextTick: typeof nextTick
+  $watch(
+    source: string | Function,
+    cb: Function,
+    options?: WatchOptions
+  ): WatchStopHandle
+} & Readonly<P> &
+  ShallowUnwrapRef<B> &
+  UnwrapNestedRefs<D> &
+  ExtractComputedReturns<C> &
+  M &
+  ComponentCustomProperties
+
+type MergedHook<T = () => void> = T | T[]
+
+export type MergedComponentOptionsOverride = {
+  beforeCreate?: MergedHook
+  created?: MergedHook
+  beforeMount?: MergedHook
+  mounted?: MergedHook
+  beforeUpdate?: MergedHook
+  updated?: MergedHook
+  activated?: MergedHook
+  deactivated?: MergedHook
+  /** @deprecated use `beforeUnmount` instead */
+  beforeDestroy?: MergedHook
+  beforeUnmount?: MergedHook
+  /** @deprecated use `unmounted` instead */
+  destroyed?: MergedHook
+  unmounted?: MergedHook
+  renderTracked?: MergedHook<DebuggerHook>
+  renderTriggered?: MergedHook<DebuggerHook>
+  errorCaptured?: MergedHook<ErrorCapturedHook>
+}
+
+export type DebuggerHook = (e: DebuggerEvent) => void
+
+export type ErrorCapturedHook<TError = unknown> = (
+  err: TError,
+  instance: ComponentPublicInstance | null,
+  info: string
+) => boolean | void
+
+export type ComponentPublicInstanceConstructor<
+  T extends ComponentPublicInstance<
+    Props,
+    RawBindings,
+    D,
+    C,
+    M
+  > = ComponentPublicInstance<any, any, any>,
+  Props = any,
+  RawBindings = any,
+  D = any,
+  C extends ComputedOptions = ComputedOptions,
+  M extends MethodOptions = MethodOptions
+> = {
+  new (...args: any[]): T
+}

+ 70 - 12
types/v3-define-component.d.ts

@@ -1,15 +1,72 @@
-import { ComponentPropsOptions } from './v3-component-props'
+import { Component } from '..'
+import {
+  ComponentPropsOptions,
+  ExtractDefaultPropTypes,
+  ExtractPropTypes
+} from './v3-component-props'
 import {
   MethodOptions,
   ComputedOptions,
   ComponentOptionsWithoutProps,
   ComponentOptionsWithArrayProps,
-  ComponentOptionsWithProps
+  ComponentOptionsWithProps,
+  ComponentOptionsMixin,
+  ComponentOptionsBase
 } from './v3-component-options'
-import { VueProxy } from './v3-component-proxy'
+import {
+  ComponentPublicInstanceConstructor,
+  CreateComponentPublicInstance
+} from './v3-component-public-instance'
 import { Data, HasDefined } from './common'
 import { EmitsOptions } from './v3-setup-context'
 
+type DefineComponent<
+  PropsOrPropOptions = {},
+  RawBindings = {},
+  D = {},
+  C extends ComputedOptions = ComputedOptions,
+  M extends MethodOptions = MethodOptions,
+  Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
+  Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
+  E extends EmitsOptions = {},
+  EE extends string = string,
+  Props = Readonly<
+    PropsOrPropOptions extends ComponentPropsOptions
+      ? ExtractPropTypes<PropsOrPropOptions>
+      : PropsOrPropOptions
+  >,
+  Defaults = ExtractDefaultPropTypes<PropsOrPropOptions>
+> = ComponentPublicInstanceConstructor<
+  CreateComponentPublicInstance<
+    Props,
+    RawBindings,
+    D,
+    C,
+    M,
+    Mixin,
+    Extends,
+    E,
+    Props,
+    Defaults,
+    true
+  > &
+    Props
+> &
+  ComponentOptionsBase<
+    Props,
+    RawBindings,
+    D,
+    C,
+    M,
+    Mixin,
+    Extends,
+    E,
+    EE,
+    Defaults
+  > & {
+    props: PropsOrPropOptions
+  }
+
 /**
  * overload 1: object format with no props
  */
@@ -18,8 +75,8 @@ export function defineComponent<
   D = Data,
   C extends ComputedOptions = {},
   M extends MethodOptions = {},
-  Mixin = {},
-  Extends = {},
+  Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
+  Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
   Emits extends EmitsOptions = {},
   EmitsNames extends string = string
 >(
@@ -34,7 +91,8 @@ export function defineComponent<
     Emits,
     EmitsNames
   >
-): VueProxy<{}, RawBindings, D, C, M, Mixin, Extends, Emits>
+): DefineComponent<{}, RawBindings, D, C, M, Mixin, Extends, Emits>
+
 /**
  * overload 2: object format with array props declaration
  * props inferred as `{ [key in PropNames]?: any }`
@@ -47,8 +105,8 @@ export function defineComponent<
   D = Data,
   C extends ComputedOptions = {},
   M extends MethodOptions = {},
-  Mixin = {},
-  Extends = {},
+  Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
+  Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
   Emits extends EmitsOptions = {},
   EmitsNames extends string = string,
   PropsOptions extends ComponentPropsOptions = ComponentPropsOptions
@@ -64,7 +122,7 @@ export function defineComponent<
     Emits,
     EmitsNames
   >
-): VueProxy<
+): DefineComponent<
   Readonly<{ [key in PropNames]?: any }>,
   RawBindings,
   D,
@@ -86,8 +144,8 @@ export function defineComponent<
   D = Data,
   C extends ComputedOptions = {},
   M extends MethodOptions = {},
-  Mixin = {},
-  Extends = {},
+  Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
+  Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
   Emits extends EmitsOptions = {},
   EmitsNames extends string = string,
   PropsOptions extends ComponentPropsOptions = ComponentPropsOptions
@@ -116,4 +174,4 @@ export function defineComponent<
         Emits,
         EmitsNames
       >
-): VueProxy<PropsOptions, RawBindings, D, C, M, Mixin, Extends, Emits>
+): DefineComponent<PropsOptions, RawBindings, D, C, M, Mixin, Extends, Emits>

+ 0 - 6
types/v3-setup-context.d.ts

@@ -29,12 +29,6 @@ export type EmitFn<
       }[Event]
     >
 
-export type ComponentRenderEmitFn<
-  Options = ObjectEmitsOptions,
-  Event extends keyof Options = keyof Options,
-  T extends Vue | void = void
-> = EmitFn<Options, Event, T>
-
 export interface SetupContext<E extends EmitsOptions = {}> {
   attrs: Data
   slots: Slots

+ 13 - 0
types/vnode.d.ts

@@ -1,6 +1,19 @@
 import { Vue } from './vue'
 import { DirectiveFunction, DirectiveOptions } from './options'
 
+/**
+ * For extending allowed non-declared props on components in TSX
+ */
+export interface ComponentCustomProps {}
+
+/**
+ * Default allowed non-declared props on component in TSX
+ */
+export interface AllowedComponentProps {
+  class?: unknown
+  style?: unknown
+}
+
 export type ScopedSlot = (props: any) => ScopedSlotReturnValue
 type ScopedSlotReturnValue =
   | VNode