BREAKING CHANGE: The type of `defineComponent()` when passing in a function has changed. This overload signature is rarely used in practice and the breakage will be minimal, so repurposing it to something more useful should be worth it.
close #3102
})
describe('type inference w/ direct setup function', () => {
- const MyComponent = defineComponent((_props: { msg: string }) => {})
+ const MyComponent = defineComponent((_props: { msg: string }) => () => {})
expectType<JSX.Element>(<MyComponent msg="foo" />)
// @ts-expect-error
;<MyComponent />
})
})
+describe('function syntax w/ generics', () => {
+ 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>
+ )
+ }
+ )
+
+ expectType<JSX.Element>(<Comp msg="fse" list={['foo']} />)
+ expectType<JSX.Element>(<Comp msg={123} list={[123]} />)
+
+ expectType<JSX.Element>(
+ // @ts-expect-error missing prop
+ <Comp msg={123} />
+ )
+
+ expectType<JSX.Element>(
+ // @ts-expect-error generics don't match
+ <Comp msg="fse" list={[123]} />
+ )
+ expectType<JSX.Element>(
+ // @ts-expect-error generics don't match
+ <Comp msg={123} list={['123']} />
+ )
+})
+
+describe('function syntax w/ emits', () => {
+ const Foo = defineComponent(
+ (props: { msg: string }, ctx) => {
+ ctx.emit('foo')
+ // @ts-expect-error
+ ctx.emit('bar')
+ return () => {}
+ },
+ {
+ emits: ['foo']
+ }
+ )
+ expectType<JSX.Element>(<Foo msg="hi" onFoo={() => {}} />)
+ // @ts-expect-error
+ expectType<JSX.Element>(<Foo msg="hi" onBar={() => {}} />)
+})
+
+describe('function syntax w/ runtime props', () => {
+ // with runtime props, the runtime props must match
+ // manual type declaration
+ defineComponent(
+ (_props: { msg: string }) => {
+ return () => {}
+ },
+ {
+ props: ['msg']
+ }
+ )
+
+ defineComponent(
+ <T extends string>(_props: { msg: T }) => {
+ return () => {}
+ },
+ {
+ props: ['msg']
+ }
+ )
+
+ defineComponent(
+ <T extends string>(_props: { msg: T }) => {
+ return () => {}
+ },
+ {
+ props: {
+ msg: String
+ }
+ }
+ )
+
+ // @ts-expect-error string prop names don't match
+ defineComponent(
+ (_props: { msg: string }) => {
+ return () => {}
+ },
+ {
+ props: ['bar']
+ }
+ )
+
+ // @ts-expect-error prop type mismatch
+ defineComponent(
+ (_props: { msg: string }) => {
+ return () => {}
+ },
+ {
+ props: {
+ msg: Number
+ }
+ }
+ )
+
+ // @ts-expect-error prop keys don't match
+ defineComponent(
+ (_props: { msg: string }, ctx) => {
+ return () => {}
+ },
+ {
+ props: {
+ msg: String,
+ bar: String
+ }
+ }
+ )
+})
+
// check if defineComponent can be exported
export default {
// function components
- a: defineComponent(_ => h('div')),
+ a: defineComponent(_ => () => h('div')),
// no props
b: defineComponent({
data() {
describe('describeComponent extends Component', () => {
// functional
expectAssignable<Component>(
- defineComponent((_props: { foo?: string; bar: number }) => {})
+ defineComponent((_props: { foo?: string; bar: number }) => () => {})
)
// typed props
expect(serializeInner(root)).toBe(`<div>4</div>`)
})
- test('component’s own methods have higher priority than global properties', async () => {
+ test("component's own methods have higher priority than global properties", async () => {
const app = createApp({
methods: {
foo() {
test('mixins', () => {
const calls: string[] = []
- const mixinA = {
+ const mixinA = defineComponent({
data() {
return {
a: 1
mounted() {
calls.push('mixinA mounted')
}
- }
- const mixinB = {
+ })
+ const mixinB = defineComponent({
props: {
bP: {
type: String
mounted() {
calls.push('mixinB mounted')
}
- }
+ })
const mixinC = defineComponent({
props: ['cP1', 'cP2'],
data() {
props: {
aaa: String
},
- mixins: [defineComponent(mixinA), defineComponent(mixinB), mixinC],
+ mixins: [mixinA, mixinB, mixinC],
data() {
return {
c: 4,
])
})
+ test('unlikely mixin usage', () => {
+ const MixinA = {
+ data() {}
+ }
+ const MixinB = {
+ data() {}
+ }
+ defineComponent({
+ // @ts-expect-error edge case after #7963, unlikely to happen in practice
+ // since the user will want to type the mixins themselves.
+ mixins: [defineComponent(MixinA), defineComponent(MixinB)],
+ // @ts-expect-error
+ data() {}
+ })
+ })
+
test('chained extends in mixins', () => {
const calls: string[] = []
test('extends', () => {
const calls: string[] = []
- const Base = {
+ const Base = defineComponent({
data() {
return {
a: 1,
expect(this.b).toBe(2)
calls.push('base')
}
- }
+ })
const Comp = defineComponent({
- extends: defineComponent(Base),
+ extends: Base,
data() {
return {
b: 2
test('extends with mixins', () => {
const calls: string[] = []
- const Base = {
+ const Base = defineComponent({
data() {
return {
a: 1,
expect(this.c).toBe(2)
calls.push('base')
}
- }
- const Mixin = {
+ })
+ const Mixin = defineComponent({
data() {
return {
b: true,
expect(this.c).toBe(2)
calls.push('mixin')
}
- }
+ })
const Comp = defineComponent({
- extends: defineComponent(Base),
- mixins: [defineComponent(Mixin)],
+ extends: Base,
+ mixins: [Mixin],
data() {
return {
c: 2
ComponentOptionsMixin,
RenderFunction,
ComponentOptionsBase,
- ComponentInjectOptions
+ ComponentInjectOptions,
+ ComponentOptions
} from './componentOptions'
import {
SetupContext,
import {
ExtractPropTypes,
ComponentPropsOptions,
- ExtractDefaultPropTypes
+ ExtractDefaultPropTypes,
+ ComponentObjectPropsOptions
} from './componentProps'
import { EmitsOptions, EmitsToProps } from './componentEmits'
-import { isFunction } from '@vue/shared'
+import { extend, isFunction } from '@vue/shared'
import { VNodeProps } from './vnode'
import {
CreateComponentPublicInstance,
// overload 1: direct setup function
// (uses user defined props interface)
-export function defineComponent<Props, RawBindings = object>(
+export function defineComponent<
+ Props extends Record<string, any>,
+ E extends EmitsOptions = {},
+ EE extends string = string
+>(
+ setup: (
+ props: Props,
+ ctx: SetupContext<E>
+ ) => RenderFunction | Promise<RenderFunction>,
+ options?: Pick<ComponentOptions, 'name' | 'inheritAttrs'> & {
+ props?: (keyof Props)[]
+ emits?: E | EE[]
+ }
+): (props: Props & EmitsToProps<E>) => any
+export function defineComponent<
+ Props extends Record<string, any>,
+ E extends EmitsOptions = {},
+ EE extends string = string
+>(
setup: (
- props: Readonly<Props>,
- ctx: SetupContext
- ) => RawBindings | RenderFunction
-): DefineComponent<Props, RawBindings>
+ props: Props,
+ ctx: SetupContext<E>
+ ) => RenderFunction | Promise<RenderFunction>,
+ options?: Pick<ComponentOptions, 'name' | 'inheritAttrs'> & {
+ props?: ComponentObjectPropsOptions<Props>
+ emits?: E | EE[]
+ }
+): (props: Props & EmitsToProps<E>) => any
// overload 2: object format with no props
// (uses user defined props interface)
): DefineComponent<PropsOptions, RawBindings, D, C, M, Mixin, Extends, E, EE>
// implementation, close to no-op
-export function defineComponent(options: unknown) {
- return isFunction(options) ? { setup: options, name: options.name } : options
+export function defineComponent(
+ options: unknown,
+ extraOptions?: ComponentOptions
+) {
+ return isFunction(options)
+ ? extend({}, extraOptions, { setup: options, name: options.name })
+ : options
}