feat(types): mixins/extends support in TypeScript (#626)
This commit is contained in:
parent
97dedebd80
commit
d3c436ae2e
@ -414,11 +414,11 @@ describe('reactivity/collections', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('thisArg', () => {
|
it('thisArg', () => {
|
||||||
const raw = new Set([ 'value' ])
|
const raw = new Set(['value'])
|
||||||
const proxy = reactive(raw)
|
const proxy = reactive(raw)
|
||||||
const thisArg = {}
|
const thisArg = {}
|
||||||
let count = 0
|
let count = 0
|
||||||
proxy.forEach(function (this :{}, value, _, set) {
|
proxy.forEach(function(this: {}, value, _, set) {
|
||||||
++count
|
++count
|
||||||
expect(this).toBe(thisArg)
|
expect(this).toBe(thisArg)
|
||||||
expect(value).toBe('value')
|
expect(value).toBe('value')
|
||||||
|
@ -49,7 +49,7 @@ function createGetter(isReadonly = false, shallow = false) {
|
|||||||
}
|
}
|
||||||
const res = Reflect.get(target, key, receiver)
|
const res = Reflect.get(target, key, receiver)
|
||||||
|
|
||||||
if (isSymbol(key) && builtInSymbols.has(key) || key === '__proto__') {
|
if ((isSymbol(key) && builtInSymbols.has(key)) || key === '__proto__') {
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -443,6 +443,11 @@ describe('api: options', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const mixinB = {
|
const mixinB = {
|
||||||
|
props: {
|
||||||
|
bP: {
|
||||||
|
type: String
|
||||||
|
}
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
b: 2
|
b: 2
|
||||||
@ -452,40 +457,65 @@ describe('api: options', () => {
|
|||||||
calls.push('mixinB created')
|
calls.push('mixinB created')
|
||||||
expect(this.a).toBe(1)
|
expect(this.a).toBe(1)
|
||||||
expect(this.b).toBe(2)
|
expect(this.b).toBe(2)
|
||||||
|
expect(this.bP).toBeUndefined()
|
||||||
expect(this.c).toBe(3)
|
expect(this.c).toBe(3)
|
||||||
|
expect(this.cP1).toBeUndefined()
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
calls.push('mixinB mounted')
|
calls.push('mixinB mounted')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const Comp = {
|
const mixinC = defineComponent({
|
||||||
mixins: [mixinA, mixinB],
|
props: ['cP1', 'cP2'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
c: 3
|
c: 3
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created(this: any) {
|
created() {
|
||||||
|
calls.push('mixinC created')
|
||||||
|
expect(this.c).toBe(3)
|
||||||
|
expect(this.cP1).toBeUndefined()
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
calls.push('mixinC mounted')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const Comp = defineComponent({
|
||||||
|
props: {
|
||||||
|
aaa: String
|
||||||
|
},
|
||||||
|
mixins: [defineComponent(mixinA), defineComponent(mixinB), mixinC],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
z: 4
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
calls.push('comp created')
|
calls.push('comp created')
|
||||||
expect(this.a).toBe(1)
|
expect(this.a).toBe(1)
|
||||||
expect(this.b).toBe(2)
|
expect(this.b).toBe(2)
|
||||||
|
expect(this.bP).toBeUndefined()
|
||||||
expect(this.c).toBe(3)
|
expect(this.c).toBe(3)
|
||||||
|
expect(this.cP2).toBeUndefined()
|
||||||
|
expect(this.z).toBe(4)
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
calls.push('comp mounted')
|
calls.push('comp mounted')
|
||||||
},
|
},
|
||||||
render(this: any) {
|
render() {
|
||||||
return `${this.a}${this.b}${this.c}`
|
return `${this.a}${this.b}${this.c}`
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
|
||||||
expect(renderToString(h(Comp))).toBe(`123`)
|
expect(renderToString(h(Comp))).toBe(`123`)
|
||||||
expect(calls).toEqual([
|
expect(calls).toEqual([
|
||||||
'mixinA created',
|
'mixinA created',
|
||||||
'mixinB created',
|
'mixinB created',
|
||||||
|
'mixinC created',
|
||||||
'comp created',
|
'comp created',
|
||||||
'mixinA mounted',
|
'mixinA mounted',
|
||||||
'mixinB mounted',
|
'mixinB mounted',
|
||||||
|
'mixinC mounted',
|
||||||
'comp mounted'
|
'comp mounted'
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
@ -498,12 +528,17 @@ describe('api: options', () => {
|
|||||||
a: 1
|
a: 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
methods: {
|
||||||
|
sayA() {}
|
||||||
|
},
|
||||||
|
mounted(this: any) {
|
||||||
|
expect(this.a).toBe(1)
|
||||||
|
expect(this.b).toBe(2)
|
||||||
calls.push('base')
|
calls.push('base')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const Comp = {
|
const Comp = defineComponent({
|
||||||
extends: Base,
|
extends: defineComponent(Base),
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
b: 2
|
b: 2
|
||||||
@ -512,15 +547,66 @@ describe('api: options', () => {
|
|||||||
mounted() {
|
mounted() {
|
||||||
calls.push('comp')
|
calls.push('comp')
|
||||||
},
|
},
|
||||||
render(this: any) {
|
render() {
|
||||||
return `${this.a}${this.b}`
|
return `${this.a}${this.b}`
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
|
||||||
expect(renderToString(h(Comp))).toBe(`12`)
|
expect(renderToString(h(Comp))).toBe(`12`)
|
||||||
expect(calls).toEqual(['base', 'comp'])
|
expect(calls).toEqual(['base', 'comp'])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('extends with mixins', () => {
|
||||||
|
const calls: string[] = []
|
||||||
|
const Base = {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
a: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
sayA() {}
|
||||||
|
},
|
||||||
|
mounted(this: any) {
|
||||||
|
expect(this.a).toBe(1)
|
||||||
|
expect(this.b).toBeTruthy()
|
||||||
|
expect(this.c).toBe(2)
|
||||||
|
calls.push('base')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const Base2 = {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
b: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted(this: any) {
|
||||||
|
expect(this.a).toBe(1)
|
||||||
|
expect(this.b).toBeTruthy()
|
||||||
|
expect(this.c).toBe(2)
|
||||||
|
calls.push('base2')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const Comp = defineComponent({
|
||||||
|
extends: defineComponent(Base),
|
||||||
|
mixins: [defineComponent(Base2)],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
c: 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
calls.push('comp')
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
return `${this.a}${this.b}${this.c}`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(renderToString(h(Comp))).toBe(`1true2`)
|
||||||
|
expect(calls).toEqual(['base', 'base2', 'comp'])
|
||||||
|
})
|
||||||
|
|
||||||
test('accessing setup() state from options', async () => {
|
test('accessing setup() state from options', async () => {
|
||||||
const Comp = defineComponent({
|
const Comp = defineComponent({
|
||||||
setup() {
|
setup() {
|
||||||
|
@ -4,10 +4,14 @@ import {
|
|||||||
ComponentOptionsWithoutProps,
|
ComponentOptionsWithoutProps,
|
||||||
ComponentOptionsWithArrayProps,
|
ComponentOptionsWithArrayProps,
|
||||||
ComponentOptionsWithObjectProps,
|
ComponentOptionsWithObjectProps,
|
||||||
|
ComponentOptionsMixin,
|
||||||
RenderFunction
|
RenderFunction
|
||||||
} from './componentOptions'
|
} from './componentOptions'
|
||||||
import { SetupContext, FunctionalComponent } from './component'
|
import { SetupContext, FunctionalComponent } from './component'
|
||||||
import { ComponentPublicInstance } from './componentProxy'
|
import {
|
||||||
|
CreateComponentPublicInstance,
|
||||||
|
ComponentPublicInstanceConstructor
|
||||||
|
} from './componentProxy'
|
||||||
import { ExtractPropTypes, ComponentPropsOptions } from './componentProps'
|
import { ExtractPropTypes, ComponentPropsOptions } from './componentProps'
|
||||||
import { EmitsOptions } from './componentEmits'
|
import { EmitsOptions } from './componentEmits'
|
||||||
import { isFunction } from '@vue/shared'
|
import { isFunction } from '@vue/shared'
|
||||||
@ -25,17 +29,21 @@ export function defineComponent<Props, RawBindings = object>(
|
|||||||
props: Readonly<Props>,
|
props: Readonly<Props>,
|
||||||
ctx: SetupContext
|
ctx: SetupContext
|
||||||
) => RawBindings | RenderFunction
|
) => RawBindings | RenderFunction
|
||||||
): {
|
): ComponentPublicInstanceConstructor<
|
||||||
new (): ComponentPublicInstance<
|
CreateComponentPublicInstance<
|
||||||
Props,
|
Props,
|
||||||
RawBindings,
|
RawBindings,
|
||||||
{},
|
{},
|
||||||
{},
|
{},
|
||||||
{},
|
{},
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
{},
|
||||||
// public props
|
// public props
|
||||||
VNodeProps & Props
|
VNodeProps & Props
|
||||||
>
|
>
|
||||||
} & FunctionalComponent<Props>
|
> &
|
||||||
|
FunctionalComponent<Props>
|
||||||
|
|
||||||
// overload 2: object format with no props
|
// overload 2: object format with no props
|
||||||
// (uses user defined props interface)
|
// (uses user defined props interface)
|
||||||
@ -46,21 +54,46 @@ export function defineComponent<
|
|||||||
D = {},
|
D = {},
|
||||||
C extends ComputedOptions = {},
|
C extends ComputedOptions = {},
|
||||||
M extends MethodOptions = {},
|
M extends MethodOptions = {},
|
||||||
|
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
|
||||||
|
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
|
||||||
E extends EmitsOptions = Record<string, any>,
|
E extends EmitsOptions = Record<string, any>,
|
||||||
EE extends string = string
|
EE extends string = string
|
||||||
>(
|
>(
|
||||||
options: ComponentOptionsWithoutProps<Props, RawBindings, D, C, M, E, EE>
|
options: ComponentOptionsWithoutProps<
|
||||||
): {
|
|
||||||
new (): ComponentPublicInstance<
|
|
||||||
Props,
|
Props,
|
||||||
RawBindings,
|
RawBindings,
|
||||||
D,
|
D,
|
||||||
C,
|
C,
|
||||||
M,
|
M,
|
||||||
|
Mixin,
|
||||||
|
Extends,
|
||||||
|
E,
|
||||||
|
EE
|
||||||
|
>
|
||||||
|
): ComponentPublicInstanceConstructor<
|
||||||
|
CreateComponentPublicInstance<
|
||||||
|
Props,
|
||||||
|
RawBindings,
|
||||||
|
D,
|
||||||
|
C,
|
||||||
|
M,
|
||||||
|
Mixin,
|
||||||
|
Extends,
|
||||||
E,
|
E,
|
||||||
VNodeProps & Props
|
VNodeProps & Props
|
||||||
>
|
>
|
||||||
} & ComponentOptionsWithoutProps<Props, RawBindings, D, C, M, E, EE>
|
> &
|
||||||
|
ComponentOptionsWithoutProps<
|
||||||
|
Props,
|
||||||
|
RawBindings,
|
||||||
|
D,
|
||||||
|
C,
|
||||||
|
M,
|
||||||
|
Mixin,
|
||||||
|
Extends,
|
||||||
|
E,
|
||||||
|
EE
|
||||||
|
>
|
||||||
|
|
||||||
// overload 3: object format with array props declaration
|
// overload 3: object format with array props declaration
|
||||||
// props inferred as { [key in PropNames]?: any }
|
// props inferred as { [key in PropNames]?: any }
|
||||||
@ -71,6 +104,8 @@ export function defineComponent<
|
|||||||
D,
|
D,
|
||||||
C extends ComputedOptions = {},
|
C extends ComputedOptions = {},
|
||||||
M extends MethodOptions = {},
|
M extends MethodOptions = {},
|
||||||
|
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
|
||||||
|
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
|
||||||
E extends EmitsOptions = Record<string, any>,
|
E extends EmitsOptions = Record<string, any>,
|
||||||
EE extends string = string
|
EE extends string = string
|
||||||
>(
|
>(
|
||||||
@ -80,13 +115,36 @@ export function defineComponent<
|
|||||||
D,
|
D,
|
||||||
C,
|
C,
|
||||||
M,
|
M,
|
||||||
|
Mixin,
|
||||||
|
Extends,
|
||||||
|
E,
|
||||||
|
EE
|
||||||
|
>
|
||||||
|
): ComponentPublicInstanceConstructor<
|
||||||
|
// array props technically doesn't place any contraints on props in TSX before,
|
||||||
|
// but now we can export array props in TSX
|
||||||
|
CreateComponentPublicInstance<
|
||||||
|
Readonly<{ [key in PropNames]?: any }>,
|
||||||
|
RawBindings,
|
||||||
|
D,
|
||||||
|
C,
|
||||||
|
M,
|
||||||
|
Mixin,
|
||||||
|
Extends,
|
||||||
|
E
|
||||||
|
>
|
||||||
|
> &
|
||||||
|
ComponentOptionsWithArrayProps<
|
||||||
|
PropNames,
|
||||||
|
RawBindings,
|
||||||
|
D,
|
||||||
|
C,
|
||||||
|
M,
|
||||||
|
Mixin,
|
||||||
|
Extends,
|
||||||
E,
|
E,
|
||||||
EE
|
EE
|
||||||
>
|
>
|
||||||
): {
|
|
||||||
// array props technically doesn't place any constraints on props in TSX
|
|
||||||
new (): ComponentPublicInstance<VNodeProps, RawBindings, D, C, M, E>
|
|
||||||
} & ComponentOptionsWithArrayProps<PropNames, RawBindings, D, C, M, E, EE>
|
|
||||||
|
|
||||||
// overload 4: object format with object props declaration
|
// overload 4: object format with object props declaration
|
||||||
// see `ExtractPropTypes` in ./componentProps.ts
|
// see `ExtractPropTypes` in ./componentProps.ts
|
||||||
@ -98,6 +156,8 @@ export function defineComponent<
|
|||||||
D,
|
D,
|
||||||
C extends ComputedOptions = {},
|
C extends ComputedOptions = {},
|
||||||
M extends MethodOptions = {},
|
M extends MethodOptions = {},
|
||||||
|
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
|
||||||
|
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
|
||||||
E extends EmitsOptions = Record<string, any>,
|
E extends EmitsOptions = Record<string, any>,
|
||||||
EE extends string = string
|
EE extends string = string
|
||||||
>(
|
>(
|
||||||
@ -107,20 +167,35 @@ export function defineComponent<
|
|||||||
D,
|
D,
|
||||||
C,
|
C,
|
||||||
M,
|
M,
|
||||||
|
Mixin,
|
||||||
|
Extends,
|
||||||
E,
|
E,
|
||||||
EE
|
EE
|
||||||
>
|
>
|
||||||
): {
|
): ComponentPublicInstanceConstructor<
|
||||||
new (): ComponentPublicInstance<
|
CreateComponentPublicInstance<
|
||||||
ExtractPropTypes<PropsOptions>,
|
ExtractPropTypes<PropsOptions>,
|
||||||
RawBindings,
|
RawBindings,
|
||||||
D,
|
D,
|
||||||
C,
|
C,
|
||||||
M,
|
M,
|
||||||
|
Mixin,
|
||||||
|
Extends,
|
||||||
E,
|
E,
|
||||||
VNodeProps & ExtractPropTypes<PropsOptions, false>
|
VNodeProps & ExtractPropTypes<PropsOptions, false>
|
||||||
>
|
>
|
||||||
} & ComponentOptionsWithObjectProps<PropsOptions, RawBindings, D, C, M, E, EE>
|
> &
|
||||||
|
ComponentOptionsWithObjectProps<
|
||||||
|
PropsOptions,
|
||||||
|
RawBindings,
|
||||||
|
D,
|
||||||
|
C,
|
||||||
|
M,
|
||||||
|
Mixin,
|
||||||
|
Extends,
|
||||||
|
E,
|
||||||
|
EE
|
||||||
|
>
|
||||||
|
|
||||||
// implementation, close to no-op
|
// implementation, close to no-op
|
||||||
export function defineComponent(options: unknown) {
|
export function defineComponent(options: unknown) {
|
||||||
|
@ -7,6 +7,7 @@ import {
|
|||||||
shallowReadonly
|
shallowReadonly
|
||||||
} from '@vue/reactivity'
|
} from '@vue/reactivity'
|
||||||
import {
|
import {
|
||||||
|
CreateComponentPublicInstance,
|
||||||
ComponentPublicInstance,
|
ComponentPublicInstance,
|
||||||
PublicInstanceProxyHandlers,
|
PublicInstanceProxyHandlers,
|
||||||
RuntimeCompiledPublicInstanceProxyHandlers,
|
RuntimeCompiledPublicInstanceProxyHandlers,
|
||||||
@ -96,7 +97,15 @@ export type Component = ComponentOptions | FunctionalComponent<any>
|
|||||||
// The constructor type is an artificial type returned by defineComponent().
|
// The constructor type is an artificial type returned by defineComponent().
|
||||||
export type PublicAPIComponent =
|
export type PublicAPIComponent =
|
||||||
| Component
|
| Component
|
||||||
| { new (...args: any[]): ComponentPublicInstance<any, any, any, any, any> }
|
| {
|
||||||
|
new (...args: any[]): CreateComponentPublicInstance<
|
||||||
|
any,
|
||||||
|
any,
|
||||||
|
any,
|
||||||
|
any,
|
||||||
|
any
|
||||||
|
>
|
||||||
|
}
|
||||||
|
|
||||||
export { ComponentOptions }
|
export { ComponentOptions }
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@ import { ComponentInternalInstance } from './component'
|
|||||||
import { callWithAsyncErrorHandling, ErrorCodes } from './errorHandling'
|
import { callWithAsyncErrorHandling, ErrorCodes } from './errorHandling'
|
||||||
import { warn } from './warning'
|
import { warn } from './warning'
|
||||||
import { normalizePropsOptions } from './componentProps'
|
import { normalizePropsOptions } from './componentProps'
|
||||||
|
import { UnionToIntersection } from './helpers/typeUtils'
|
||||||
|
|
||||||
export type ObjectEmitsOptions = Record<
|
export type ObjectEmitsOptions = Record<
|
||||||
string,
|
string,
|
||||||
@ -19,12 +20,6 @@ export type ObjectEmitsOptions = Record<
|
|||||||
>
|
>
|
||||||
export type EmitsOptions = ObjectEmitsOptions | string[]
|
export type EmitsOptions = ObjectEmitsOptions | string[]
|
||||||
|
|
||||||
type UnionToIntersection<U> = (U extends any
|
|
||||||
? (k: U) => void
|
|
||||||
: never) extends ((k: infer I) => void)
|
|
||||||
? I
|
|
||||||
: never
|
|
||||||
|
|
||||||
export type EmitFn<
|
export type EmitFn<
|
||||||
Options = ObjectEmitsOptions,
|
Options = ObjectEmitsOptions,
|
||||||
Event extends keyof Options = keyof Options
|
Event extends keyof Options = keyof Options
|
||||||
|
@ -48,7 +48,10 @@ import {
|
|||||||
} from './componentProps'
|
} from './componentProps'
|
||||||
import { EmitsOptions } from './componentEmits'
|
import { EmitsOptions } from './componentEmits'
|
||||||
import { Directive } from './directives'
|
import { Directive } from './directives'
|
||||||
import { ComponentPublicInstance } from './componentProxy'
|
import {
|
||||||
|
CreateComponentPublicInstance,
|
||||||
|
ComponentPublicInstance
|
||||||
|
} from './componentProxy'
|
||||||
import { warn } from './warning'
|
import { warn } from './warning'
|
||||||
import { VNodeChild } from './vnode'
|
import { VNodeChild } from './vnode'
|
||||||
|
|
||||||
@ -78,10 +81,12 @@ export interface ComponentOptionsBase<
|
|||||||
D,
|
D,
|
||||||
C extends ComputedOptions,
|
C extends ComputedOptions,
|
||||||
M extends MethodOptions,
|
M extends MethodOptions,
|
||||||
|
Mixin extends ComponentOptionsMixin,
|
||||||
|
Extends extends ComponentOptionsMixin,
|
||||||
E extends EmitsOptions,
|
E extends EmitsOptions,
|
||||||
EE extends string = string
|
EE extends string = string
|
||||||
>
|
>
|
||||||
extends LegacyOptions<Props, D, C, M>,
|
extends LegacyOptions<Props, D, C, M, Mixin, Extends>,
|
||||||
SFCInternalOptions,
|
SFCInternalOptions,
|
||||||
ComponentCustomOptions {
|
ComponentCustomOptions {
|
||||||
setup?: (
|
setup?: (
|
||||||
@ -148,12 +153,24 @@ export type ComponentOptionsWithoutProps<
|
|||||||
D = {},
|
D = {},
|
||||||
C extends ComputedOptions = {},
|
C extends ComputedOptions = {},
|
||||||
M extends MethodOptions = {},
|
M extends MethodOptions = {},
|
||||||
|
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
|
||||||
|
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
|
||||||
E extends EmitsOptions = EmitsOptions,
|
E extends EmitsOptions = EmitsOptions,
|
||||||
EE extends string = string
|
EE extends string = string
|
||||||
> = ComponentOptionsBase<Props, RawBindings, D, C, M, E, EE> & {
|
> = ComponentOptionsBase<Props, RawBindings, D, C, M, Mixin, Extends, E, EE> & {
|
||||||
props?: undefined
|
props?: undefined
|
||||||
} & ThisType<
|
} & ThisType<
|
||||||
ComponentPublicInstance<{}, RawBindings, D, C, M, E, Readonly<Props>>
|
CreateComponentPublicInstance<
|
||||||
|
{},
|
||||||
|
RawBindings,
|
||||||
|
D,
|
||||||
|
C,
|
||||||
|
M,
|
||||||
|
Mixin,
|
||||||
|
Extends,
|
||||||
|
E,
|
||||||
|
Readonly<Props>
|
||||||
|
>
|
||||||
>
|
>
|
||||||
|
|
||||||
export type ComponentOptionsWithArrayProps<
|
export type ComponentOptionsWithArrayProps<
|
||||||
@ -162,12 +179,25 @@ export type ComponentOptionsWithArrayProps<
|
|||||||
D = {},
|
D = {},
|
||||||
C extends ComputedOptions = {},
|
C extends ComputedOptions = {},
|
||||||
M extends MethodOptions = {},
|
M extends MethodOptions = {},
|
||||||
|
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
|
||||||
|
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
|
||||||
E extends EmitsOptions = EmitsOptions,
|
E extends EmitsOptions = EmitsOptions,
|
||||||
EE extends string = string,
|
EE extends string = string,
|
||||||
Props = Readonly<{ [key in PropNames]?: any }>
|
Props = Readonly<{ [key in PropNames]?: any }>
|
||||||
> = ComponentOptionsBase<Props, RawBindings, D, C, M, E, EE> & {
|
> = ComponentOptionsBase<Props, RawBindings, D, C, M, Mixin, Extends, E, EE> & {
|
||||||
props: PropNames[]
|
props: PropNames[]
|
||||||
} & ThisType<ComponentPublicInstance<Props, RawBindings, D, C, M, E>>
|
} & ThisType<
|
||||||
|
CreateComponentPublicInstance<
|
||||||
|
Props,
|
||||||
|
RawBindings,
|
||||||
|
D,
|
||||||
|
C,
|
||||||
|
M,
|
||||||
|
Mixin,
|
||||||
|
Extends,
|
||||||
|
E
|
||||||
|
>
|
||||||
|
>
|
||||||
|
|
||||||
export type ComponentOptionsWithObjectProps<
|
export type ComponentOptionsWithObjectProps<
|
||||||
PropsOptions = ComponentObjectPropsOptions,
|
PropsOptions = ComponentObjectPropsOptions,
|
||||||
@ -175,18 +205,43 @@ export type ComponentOptionsWithObjectProps<
|
|||||||
D = {},
|
D = {},
|
||||||
C extends ComputedOptions = {},
|
C extends ComputedOptions = {},
|
||||||
M extends MethodOptions = {},
|
M extends MethodOptions = {},
|
||||||
|
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
|
||||||
|
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
|
||||||
E extends EmitsOptions = EmitsOptions,
|
E extends EmitsOptions = EmitsOptions,
|
||||||
EE extends string = string,
|
EE extends string = string,
|
||||||
Props = Readonly<ExtractPropTypes<PropsOptions>>
|
Props = Readonly<ExtractPropTypes<PropsOptions>>
|
||||||
> = ComponentOptionsBase<Props, RawBindings, D, C, M, E, EE> & {
|
> = ComponentOptionsBase<Props, RawBindings, D, C, M, Mixin, Extends, E, EE> & {
|
||||||
props: PropsOptions
|
props: PropsOptions
|
||||||
} & ThisType<ComponentPublicInstance<Props, RawBindings, D, C, M, E>>
|
} & ThisType<
|
||||||
|
CreateComponentPublicInstance<
|
||||||
|
Props,
|
||||||
|
RawBindings,
|
||||||
|
D,
|
||||||
|
C,
|
||||||
|
M,
|
||||||
|
Mixin,
|
||||||
|
Extends,
|
||||||
|
E
|
||||||
|
>
|
||||||
|
>
|
||||||
|
|
||||||
export type ComponentOptions =
|
export type ComponentOptions =
|
||||||
| ComponentOptionsWithoutProps<any, any, any, any, any>
|
| ComponentOptionsWithoutProps<any, any, any, any, any>
|
||||||
| ComponentOptionsWithObjectProps<any, any, any, any, any>
|
| ComponentOptionsWithObjectProps<any, any, any, any, any>
|
||||||
| ComponentOptionsWithArrayProps<any, any, any, any, any>
|
| ComponentOptionsWithArrayProps<any, any, any, any, any>
|
||||||
|
|
||||||
|
export type ComponentOptionsMixin = ComponentOptionsBase<
|
||||||
|
any,
|
||||||
|
any,
|
||||||
|
any,
|
||||||
|
any,
|
||||||
|
any,
|
||||||
|
any,
|
||||||
|
any,
|
||||||
|
any,
|
||||||
|
any
|
||||||
|
>
|
||||||
|
|
||||||
export type ComputedOptions = Record<
|
export type ComputedOptions = Record<
|
||||||
string,
|
string,
|
||||||
ComputedGetter<any> | WritableComputedOptions<any>
|
ComputedGetter<any> | WritableComputedOptions<any>
|
||||||
@ -222,7 +277,9 @@ interface LegacyOptions<
|
|||||||
Props,
|
Props,
|
||||||
D,
|
D,
|
||||||
C extends ComputedOptions,
|
C extends ComputedOptions,
|
||||||
M extends MethodOptions
|
M extends MethodOptions,
|
||||||
|
Mixin extends ComponentOptionsMixin,
|
||||||
|
Extends extends ComponentOptionsMixin
|
||||||
> {
|
> {
|
||||||
// allow any custom options
|
// allow any custom options
|
||||||
[key: string]: any
|
[key: string]: any
|
||||||
@ -232,8 +289,8 @@ interface LegacyOptions<
|
|||||||
// since that leads to some sort of circular inference and breaks ThisType
|
// since that leads to some sort of circular inference and breaks ThisType
|
||||||
// for the entire component.
|
// for the entire component.
|
||||||
data?: (
|
data?: (
|
||||||
this: ComponentPublicInstance<Props>,
|
this: CreateComponentPublicInstance<Props>,
|
||||||
vm: ComponentPublicInstance<Props>
|
vm: CreateComponentPublicInstance<Props>
|
||||||
) => D
|
) => D
|
||||||
computed?: C
|
computed?: C
|
||||||
methods?: M
|
methods?: M
|
||||||
@ -242,8 +299,8 @@ interface LegacyOptions<
|
|||||||
inject?: ComponentInjectOptions
|
inject?: ComponentInjectOptions
|
||||||
|
|
||||||
// composition
|
// composition
|
||||||
mixins?: ComponentOptions[]
|
mixins?: Mixin[]
|
||||||
extends?: ComponentOptions
|
extends?: Extends
|
||||||
|
|
||||||
// lifecycle
|
// lifecycle
|
||||||
beforeCreate?(): void
|
beforeCreate?(): void
|
||||||
@ -261,6 +318,22 @@ interface LegacyOptions<
|
|||||||
errorCaptured?: ErrorCapturedHook
|
errorCaptured?: ErrorCapturedHook
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type OptionTypesKeys = 'P' | 'B' | 'D' | 'C' | 'M'
|
||||||
|
|
||||||
|
export type OptionTypesType<
|
||||||
|
P = {},
|
||||||
|
B = {},
|
||||||
|
D = {},
|
||||||
|
C extends ComputedOptions = {},
|
||||||
|
M extends MethodOptions = {}
|
||||||
|
> = {
|
||||||
|
P: P
|
||||||
|
B: B
|
||||||
|
D: D
|
||||||
|
C: C
|
||||||
|
M: M
|
||||||
|
}
|
||||||
|
|
||||||
const enum OptionTypes {
|
const enum OptionTypes {
|
||||||
PROPS = 'Props',
|
PROPS = 'Props',
|
||||||
DATA = 'Data',
|
DATA = 'Data',
|
||||||
|
@ -67,9 +67,11 @@ type OptionalKeys<T, MakeDefaultRequired> = Exclude<
|
|||||||
type InferPropType<T> = T extends null
|
type InferPropType<T> = T extends null
|
||||||
? any // null & true would fail to infer
|
? any // null & true would fail to infer
|
||||||
: T extends { type: null | true }
|
: T extends { type: null | true }
|
||||||
? any // somehow `ObjectConstructor` when inferred from { (): T } becomes `any`
|
? 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 }
|
: T extends ObjectConstructor | { type: ObjectConstructor }
|
||||||
? { [key: string]: any }
|
? { [key: string]: any }
|
||||||
|
: T extends BooleanConstructor | { type: BooleanConstructor }
|
||||||
|
? boolean
|
||||||
: T extends Prop<infer V> ? V : T
|
: T extends Prop<infer V> ? V : T
|
||||||
|
|
||||||
export type ExtractPropTypes<
|
export type ExtractPropTypes<
|
||||||
|
@ -14,6 +14,9 @@ import {
|
|||||||
ComponentOptionsBase,
|
ComponentOptionsBase,
|
||||||
ComputedOptions,
|
ComputedOptions,
|
||||||
MethodOptions,
|
MethodOptions,
|
||||||
|
ComponentOptionsMixin,
|
||||||
|
OptionTypesType,
|
||||||
|
OptionTypesKeys,
|
||||||
resolveMergedOptions
|
resolveMergedOptions
|
||||||
} from './componentOptions'
|
} from './componentOptions'
|
||||||
import { normalizePropsOptions } from './componentProps'
|
import { normalizePropsOptions } from './componentProps'
|
||||||
@ -24,6 +27,7 @@ import {
|
|||||||
markAttrsAccessed
|
markAttrsAccessed
|
||||||
} from './componentRenderUtils'
|
} from './componentRenderUtils'
|
||||||
import { warn } from './warning'
|
import { warn } from './warning'
|
||||||
|
import { UnionToIntersection } from './helpers/typeUtils'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom properties added to component instances in any way and can be accessed through `this`
|
* Custom properties added to component instances in any way and can be accessed through `this`
|
||||||
@ -52,6 +56,69 @@ import { warn } from './warning'
|
|||||||
*/
|
*/
|
||||||
export interface ComponentCustomProperties {}
|
export interface ComponentCustomProperties {}
|
||||||
|
|
||||||
|
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
|
||||||
|
>
|
||||||
|
? OptionTypesType<P & {}, B & {}, D & {}, C & {}, M & {}> &
|
||||||
|
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,
|
||||||
|
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>
|
||||||
|
> = ComponentPublicInstance<
|
||||||
|
PublicP,
|
||||||
|
PublicB,
|
||||||
|
PublicD,
|
||||||
|
PublicC,
|
||||||
|
PublicM,
|
||||||
|
E,
|
||||||
|
PublicProps,
|
||||||
|
ComponentOptionsBase<P, B, D, C, M, Mixin, Extends, E>
|
||||||
|
>
|
||||||
// public properties exposed on the proxy, which is used as the render context
|
// public properties exposed on the proxy, which is used as the render context
|
||||||
// in templates (as `this` in the render option)
|
// in templates (as `this` in the render option)
|
||||||
export type ComponentPublicInstance<
|
export type ComponentPublicInstance<
|
||||||
@ -61,11 +128,12 @@ export type ComponentPublicInstance<
|
|||||||
C extends ComputedOptions = {},
|
C extends ComputedOptions = {},
|
||||||
M extends MethodOptions = {},
|
M extends MethodOptions = {},
|
||||||
E extends EmitsOptions = {},
|
E extends EmitsOptions = {},
|
||||||
PublicProps = P
|
PublicProps = P,
|
||||||
|
Options = ComponentOptionsBase<any, any, any, any, any, any, any, any>
|
||||||
> = {
|
> = {
|
||||||
$: ComponentInternalInstance
|
$: ComponentInternalInstance
|
||||||
$data: D
|
$data: D
|
||||||
$props: PublicProps
|
$props: P & PublicProps
|
||||||
$attrs: Data
|
$attrs: Data
|
||||||
$refs: Data
|
$refs: Data
|
||||||
$slots: Slots
|
$slots: Slots
|
||||||
@ -73,7 +141,7 @@ export type ComponentPublicInstance<
|
|||||||
$parent: ComponentPublicInstance | null
|
$parent: ComponentPublicInstance | null
|
||||||
$emit: EmitFn<E>
|
$emit: EmitFn<E>
|
||||||
$el: any
|
$el: any
|
||||||
$options: ComponentOptionsBase<P, B, D, C, M, E>
|
$options: Options
|
||||||
$forceUpdate: ReactiveEffect
|
$forceUpdate: ReactiveEffect
|
||||||
$nextTick: typeof nextTick
|
$nextTick: typeof nextTick
|
||||||
$watch: typeof instanceWatch
|
$watch: typeof instanceWatch
|
||||||
@ -84,6 +152,12 @@ export type ComponentPublicInstance<
|
|||||||
M &
|
M &
|
||||||
ComponentCustomProperties
|
ComponentCustomProperties
|
||||||
|
|
||||||
|
export type ComponentPublicInstanceConstructor<
|
||||||
|
T extends ComponentPublicInstance
|
||||||
|
> = {
|
||||||
|
new (): T
|
||||||
|
}
|
||||||
|
|
||||||
const publicPropertiesMap: Record<
|
const publicPropertiesMap: Record<
|
||||||
string,
|
string,
|
||||||
(i: ComponentInternalInstance) => any
|
(i: ComponentInternalInstance) => any
|
||||||
|
5
packages/runtime-core/src/helpers/typeUtils.ts
Normal file
5
packages/runtime-core/src/helpers/typeUtils.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export type UnionToIntersection<U> = (U extends any
|
||||||
|
? (k: U) => void
|
||||||
|
: never) extends ((k: infer I) => void)
|
||||||
|
? I
|
||||||
|
: never
|
@ -23,6 +23,7 @@ describe('with object props', () => {
|
|||||||
ddd: string[]
|
ddd: string[]
|
||||||
eee: () => { a: string }
|
eee: () => { a: string }
|
||||||
fff: (a: number, b: string) => { a: boolean }
|
fff: (a: number, b: string) => { a: boolean }
|
||||||
|
hhh: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
type GT = string & { __brand: unknown }
|
type GT = string & { __brand: unknown }
|
||||||
@ -67,6 +68,10 @@ describe('with object props', () => {
|
|||||||
fff: {
|
fff: {
|
||||||
type: Function as PropType<(a: number, b: string) => { a: boolean }>,
|
type: Function as PropType<(a: number, b: string) => { a: boolean }>,
|
||||||
required: true
|
required: true
|
||||||
|
},
|
||||||
|
hhh: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
@ -83,6 +88,7 @@ describe('with object props', () => {
|
|||||||
expectType<ExpectedProps['ddd']>(props.ddd)
|
expectType<ExpectedProps['ddd']>(props.ddd)
|
||||||
expectType<ExpectedProps['eee']>(props.eee)
|
expectType<ExpectedProps['eee']>(props.eee)
|
||||||
expectType<ExpectedProps['fff']>(props.fff)
|
expectType<ExpectedProps['fff']>(props.fff)
|
||||||
|
expectType<ExpectedProps['hhh']>(props.hhh)
|
||||||
|
|
||||||
// @ts-expect-error props should be readonly
|
// @ts-expect-error props should be readonly
|
||||||
expectError((props.a = 1))
|
expectError((props.a = 1))
|
||||||
@ -112,6 +118,7 @@ describe('with object props', () => {
|
|||||||
expectType<ExpectedProps['ddd']>(props.ddd)
|
expectType<ExpectedProps['ddd']>(props.ddd)
|
||||||
expectType<ExpectedProps['eee']>(props.eee)
|
expectType<ExpectedProps['eee']>(props.eee)
|
||||||
expectType<ExpectedProps['fff']>(props.fff)
|
expectType<ExpectedProps['fff']>(props.fff)
|
||||||
|
expectType<ExpectedProps['hhh']>(props.hhh)
|
||||||
|
|
||||||
// @ts-expect-error props should be readonly
|
// @ts-expect-error props should be readonly
|
||||||
expectError((props.a = 1))
|
expectError((props.a = 1))
|
||||||
@ -129,6 +136,7 @@ describe('with object props', () => {
|
|||||||
expectType<ExpectedProps['ddd']>(this.ddd)
|
expectType<ExpectedProps['ddd']>(this.ddd)
|
||||||
expectType<ExpectedProps['eee']>(this.eee)
|
expectType<ExpectedProps['eee']>(this.eee)
|
||||||
expectType<ExpectedProps['fff']>(this.fff)
|
expectType<ExpectedProps['fff']>(this.fff)
|
||||||
|
expectType<ExpectedProps['hhh']>(this.hhh)
|
||||||
|
|
||||||
// @ts-expect-error props on `this` should be readonly
|
// @ts-expect-error props on `this` should be readonly
|
||||||
expectError((this.a = 1))
|
expectError((this.a = 1))
|
||||||
@ -159,6 +167,7 @@ describe('with object props', () => {
|
|||||||
ddd={['ddd']}
|
ddd={['ddd']}
|
||||||
eee={() => ({ a: 'eee' })}
|
eee={() => ({ a: 'eee' })}
|
||||||
fff={(a, b) => ({ a: a > +b })}
|
fff={(a, b) => ({ a: a > +b })}
|
||||||
|
hhh={false}
|
||||||
// should allow extraneous as attrs
|
// should allow extraneous as attrs
|
||||||
class="bar"
|
class="bar"
|
||||||
// should allow key
|
// should allow key
|
||||||
@ -210,7 +219,7 @@ describe('with object props', () => {
|
|||||||
// })
|
// })
|
||||||
|
|
||||||
describe('type inference w/ array props declaration', () => {
|
describe('type inference w/ array props declaration', () => {
|
||||||
defineComponent({
|
const MyComponent = defineComponent({
|
||||||
props: ['a', 'b'],
|
props: ['a', 'b'],
|
||||||
setup(props) {
|
setup(props) {
|
||||||
// @ts-expect-error props should be readonly
|
// @ts-expect-error props should be readonly
|
||||||
@ -231,6 +240,9 @@ describe('type inference w/ array props declaration', () => {
|
|||||||
expectType<number>(this.c)
|
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', () => {
|
describe('type inference w/ options API', () => {
|
||||||
@ -296,6 +308,275 @@ describe('type inference w/ options API', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('with mixins', () => {
|
||||||
|
const MixinA = defineComponent({
|
||||||
|
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() {
|
||||||
|
return {
|
||||||
|
d: 4
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
dC1(): number {
|
||||||
|
return this.d + this.a
|
||||||
|
},
|
||||||
|
dC2(): string {
|
||||||
|
return this.aP1 + 'dC2'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const MyComponent = defineComponent({
|
||||||
|
mixins: [MixinA, MixinB, MixinC, MixinD],
|
||||||
|
props: {
|
||||||
|
// required should make property non-void
|
||||||
|
z: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
const props = this.$props
|
||||||
|
// props
|
||||||
|
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({
|
||||||
|
props: {
|
||||||
|
mP1: {
|
||||||
|
type: String,
|
||||||
|
default: 'mP1'
|
||||||
|
},
|
||||||
|
mP2: Boolean
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
a: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const Base = defineComponent({
|
||||||
|
props: {
|
||||||
|
p1: Boolean,
|
||||||
|
p2: {
|
||||||
|
type: Number,
|
||||||
|
default: 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
b: 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
c(): number {
|
||||||
|
return this.p2 + this.b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const MyComponent = defineComponent({
|
||||||
|
extends: Base,
|
||||||
|
mixins: [Mixin],
|
||||||
|
props: {
|
||||||
|
// required should make property non-void
|
||||||
|
z: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
const props = this.$props
|
||||||
|
// props
|
||||||
|
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 p1 p2={1} z={'z'} />)
|
||||||
|
|
||||||
|
// missing required props
|
||||||
|
// @ts-expect-error
|
||||||
|
expectError(<MyComponent />)
|
||||||
|
|
||||||
|
// wrong prop types
|
||||||
|
// @ts-expect-error
|
||||||
|
expectError(<MyComponent p2={'wrong type'} z={'z'} />)
|
||||||
|
// @ts-expect-error
|
||||||
|
expectError(<MyComponent mP1={3} />)
|
||||||
|
})
|
||||||
|
|
||||||
describe('compatibility w/ createApp', () => {
|
describe('compatibility w/ createApp', () => {
|
||||||
const comp = defineComponent({})
|
const comp = defineComponent({})
|
||||||
createApp(comp).mount('#hello')
|
createApp(comp).mount('#hello')
|
||||||
|
Loading…
x
Reference in New Issue
Block a user