feat(types): mixins/extends support in TypeScript (#626)

This commit is contained in:
doly mood 2020-06-09 22:37:00 +08:00 committed by GitHub
parent 97dedebd80
commit d3c436ae2e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 656 additions and 56 deletions

View File

@ -412,13 +412,13 @@ describe('reactivity/collections', () => {
`Reactive Set contains both the raw and reactive`
).toHaveBeenWarned()
})
it('thisArg', () => {
const raw = new Set([ 'value' ])
const raw = new Set(['value'])
const proxy = reactive(raw)
const thisArg = {}
let count = 0
proxy.forEach(function (this :{}, value, _, set) {
proxy.forEach(function(this: {}, value, _, set) {
++count
expect(this).toBe(thisArg)
expect(value).toBe('value')

View File

@ -49,7 +49,7 @@ function createGetter(isReadonly = false, shallow = false) {
}
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
}

View File

@ -443,6 +443,11 @@ describe('api: options', () => {
}
}
const mixinB = {
props: {
bP: {
type: String
}
},
data() {
return {
b: 2
@ -452,40 +457,65 @@ describe('api: options', () => {
calls.push('mixinB created')
expect(this.a).toBe(1)
expect(this.b).toBe(2)
expect(this.bP).toBeUndefined()
expect(this.c).toBe(3)
expect(this.cP1).toBeUndefined()
},
mounted() {
calls.push('mixinB mounted')
}
}
const Comp = {
mixins: [mixinA, mixinB],
const mixinC = defineComponent({
props: ['cP1', 'cP2'],
data() {
return {
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')
expect(this.a).toBe(1)
expect(this.b).toBe(2)
expect(this.bP).toBeUndefined()
expect(this.c).toBe(3)
expect(this.cP2).toBeUndefined()
expect(this.z).toBe(4)
},
mounted() {
calls.push('comp mounted')
},
render(this: any) {
render() {
return `${this.a}${this.b}${this.c}`
}
}
})
expect(renderToString(h(Comp))).toBe(`123`)
expect(calls).toEqual([
'mixinA created',
'mixinB created',
'mixinC created',
'comp created',
'mixinA mounted',
'mixinB mounted',
'mixinC mounted',
'comp mounted'
])
})
@ -498,12 +528,17 @@ describe('api: options', () => {
a: 1
}
},
mounted() {
methods: {
sayA() {}
},
mounted(this: any) {
expect(this.a).toBe(1)
expect(this.b).toBe(2)
calls.push('base')
}
}
const Comp = {
extends: Base,
const Comp = defineComponent({
extends: defineComponent(Base),
data() {
return {
b: 2
@ -512,15 +547,66 @@ describe('api: options', () => {
mounted() {
calls.push('comp')
},
render(this: any) {
render() {
return `${this.a}${this.b}`
}
}
})
expect(renderToString(h(Comp))).toBe(`12`)
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 () => {
const Comp = defineComponent({
setup() {

View File

@ -4,10 +4,14 @@ import {
ComponentOptionsWithoutProps,
ComponentOptionsWithArrayProps,
ComponentOptionsWithObjectProps,
ComponentOptionsMixin,
RenderFunction
} from './componentOptions'
import { SetupContext, FunctionalComponent } from './component'
import { ComponentPublicInstance } from './componentProxy'
import {
CreateComponentPublicInstance,
ComponentPublicInstanceConstructor
} from './componentProxy'
import { ExtractPropTypes, ComponentPropsOptions } from './componentProps'
import { EmitsOptions } from './componentEmits'
import { isFunction } from '@vue/shared'
@ -25,17 +29,21 @@ export function defineComponent<Props, RawBindings = object>(
props: Readonly<Props>,
ctx: SetupContext
) => RawBindings | RenderFunction
): {
new (): ComponentPublicInstance<
): ComponentPublicInstanceConstructor<
CreateComponentPublicInstance<
Props,
RawBindings,
{},
{},
{},
{},
{},
{},
// public props
VNodeProps & Props
>
} & FunctionalComponent<Props>
> &
FunctionalComponent<Props>
// overload 2: object format with no props
// (uses user defined props interface)
@ -46,21 +54,46 @@ export function defineComponent<
D = {},
C extends ComputedOptions = {},
M extends MethodOptions = {},
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = Record<string, any>,
EE extends string = string
>(
options: ComponentOptionsWithoutProps<Props, RawBindings, D, C, M, E, EE>
): {
new (): ComponentPublicInstance<
options: ComponentOptionsWithoutProps<
Props,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
EE
>
): ComponentPublicInstanceConstructor<
CreateComponentPublicInstance<
Props,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
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
// props inferred as { [key in PropNames]?: any }
@ -71,6 +104,8 @@ export function defineComponent<
D,
C extends ComputedOptions = {},
M extends MethodOptions = {},
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = Record<string, any>,
EE extends string = string
>(
@ -80,13 +115,36 @@ export function defineComponent<
D,
C,
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,
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
// see `ExtractPropTypes` in ./componentProps.ts
@ -98,6 +156,8 @@ export function defineComponent<
D,
C extends ComputedOptions = {},
M extends MethodOptions = {},
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = Record<string, any>,
EE extends string = string
>(
@ -107,20 +167,35 @@ export function defineComponent<
D,
C,
M,
Mixin,
Extends,
E,
EE
>
): {
new (): ComponentPublicInstance<
): ComponentPublicInstanceConstructor<
CreateComponentPublicInstance<
ExtractPropTypes<PropsOptions>,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
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
export function defineComponent(options: unknown) {

View File

@ -7,6 +7,7 @@ import {
shallowReadonly
} from '@vue/reactivity'
import {
CreateComponentPublicInstance,
ComponentPublicInstance,
PublicInstanceProxyHandlers,
RuntimeCompiledPublicInstanceProxyHandlers,
@ -96,7 +97,15 @@ export type Component = ComponentOptions | FunctionalComponent<any>
// The constructor type is an artificial type returned by defineComponent().
export type PublicAPIComponent =
| Component
| { new (...args: any[]): ComponentPublicInstance<any, any, any, any, any> }
| {
new (...args: any[]): CreateComponentPublicInstance<
any,
any,
any,
any,
any
>
}
export { ComponentOptions }

View File

@ -12,6 +12,7 @@ import { ComponentInternalInstance } from './component'
import { callWithAsyncErrorHandling, ErrorCodes } from './errorHandling'
import { warn } from './warning'
import { normalizePropsOptions } from './componentProps'
import { UnionToIntersection } from './helpers/typeUtils'
export type ObjectEmitsOptions = Record<
string,
@ -19,12 +20,6 @@ export type ObjectEmitsOptions = Record<
>
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<
Options = ObjectEmitsOptions,
Event extends keyof Options = keyof Options

View File

@ -48,7 +48,10 @@ import {
} from './componentProps'
import { EmitsOptions } from './componentEmits'
import { Directive } from './directives'
import { ComponentPublicInstance } from './componentProxy'
import {
CreateComponentPublicInstance,
ComponentPublicInstance
} from './componentProxy'
import { warn } from './warning'
import { VNodeChild } from './vnode'
@ -78,10 +81,12 @@ export interface ComponentOptionsBase<
D,
C extends ComputedOptions,
M extends MethodOptions,
Mixin extends ComponentOptionsMixin,
Extends extends ComponentOptionsMixin,
E extends EmitsOptions,
EE extends string = string
>
extends LegacyOptions<Props, D, C, M>,
extends LegacyOptions<Props, D, C, M, Mixin, Extends>,
SFCInternalOptions,
ComponentCustomOptions {
setup?: (
@ -148,12 +153,24 @@ export type ComponentOptionsWithoutProps<
D = {},
C extends ComputedOptions = {},
M extends MethodOptions = {},
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = EmitsOptions,
EE extends string = string
> = ComponentOptionsBase<Props, RawBindings, D, C, M, E, EE> & {
> = ComponentOptionsBase<Props, RawBindings, D, C, M, Mixin, Extends, E, EE> & {
props?: undefined
} & ThisType<
ComponentPublicInstance<{}, RawBindings, D, C, M, E, Readonly<Props>>
CreateComponentPublicInstance<
{},
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
Readonly<Props>
>
>
export type ComponentOptionsWithArrayProps<
@ -162,12 +179,25 @@ export type ComponentOptionsWithArrayProps<
D = {},
C extends ComputedOptions = {},
M extends MethodOptions = {},
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = EmitsOptions,
EE extends string = string,
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[]
} & ThisType<ComponentPublicInstance<Props, RawBindings, D, C, M, E>>
} & ThisType<
CreateComponentPublicInstance<
Props,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E
>
>
export type ComponentOptionsWithObjectProps<
PropsOptions = ComponentObjectPropsOptions,
@ -175,18 +205,43 @@ export type ComponentOptionsWithObjectProps<
D = {},
C extends ComputedOptions = {},
M extends MethodOptions = {},
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = EmitsOptions,
EE extends string = string,
Props = Readonly<ExtractPropTypes<PropsOptions>>
> = ComponentOptionsBase<Props, RawBindings, D, C, M, E, EE> & {
> = ComponentOptionsBase<Props, RawBindings, D, C, M, Mixin, Extends, E, EE> & {
props: PropsOptions
} & ThisType<ComponentPublicInstance<Props, RawBindings, D, C, M, E>>
} & ThisType<
CreateComponentPublicInstance<
Props,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E
>
>
export type ComponentOptions =
| ComponentOptionsWithoutProps<any, any, any, any, any>
| ComponentOptionsWithObjectProps<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<
string,
ComputedGetter<any> | WritableComputedOptions<any>
@ -222,7 +277,9 @@ interface LegacyOptions<
Props,
D,
C extends ComputedOptions,
M extends MethodOptions
M extends MethodOptions,
Mixin extends ComponentOptionsMixin,
Extends extends ComponentOptionsMixin
> {
// allow any custom options
[key: string]: any
@ -232,8 +289,8 @@ interface LegacyOptions<
// since that leads to some sort of circular inference and breaks ThisType
// for the entire component.
data?: (
this: ComponentPublicInstance<Props>,
vm: ComponentPublicInstance<Props>
this: CreateComponentPublicInstance<Props>,
vm: CreateComponentPublicInstance<Props>
) => D
computed?: C
methods?: M
@ -242,8 +299,8 @@ interface LegacyOptions<
inject?: ComponentInjectOptions
// composition
mixins?: ComponentOptions[]
extends?: ComponentOptions
mixins?: Mixin[]
extends?: Extends
// lifecycle
beforeCreate?(): void
@ -261,6 +318,22 @@ interface LegacyOptions<
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 {
PROPS = 'Props',
DATA = 'Data',

View File

@ -67,10 +67,12 @@ type OptionalKeys<T, MakeDefaultRequired> = Exclude<
type InferPropType<T> = T extends null
? any // null & true would fail to infer
: 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 }
? { [key: string]: any }
: T extends Prop<infer V> ? V : T
: T extends BooleanConstructor | { type: BooleanConstructor }
? boolean
: T extends Prop<infer V> ? V : T
export type ExtractPropTypes<
O,

View File

@ -14,6 +14,9 @@ import {
ComponentOptionsBase,
ComputedOptions,
MethodOptions,
ComponentOptionsMixin,
OptionTypesType,
OptionTypesKeys,
resolveMergedOptions
} from './componentOptions'
import { normalizePropsOptions } from './componentProps'
@ -24,6 +27,7 @@ import {
markAttrsAccessed
} from './componentRenderUtils'
import { warn } from './warning'
import { UnionToIntersection } from './helpers/typeUtils'
/**
* 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 {}
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
// in templates (as `this` in the render option)
export type ComponentPublicInstance<
@ -61,11 +128,12 @@ export type ComponentPublicInstance<
C extends ComputedOptions = {},
M extends MethodOptions = {},
E extends EmitsOptions = {},
PublicProps = P
PublicProps = P,
Options = ComponentOptionsBase<any, any, any, any, any, any, any, any>
> = {
$: ComponentInternalInstance
$data: D
$props: PublicProps
$props: P & PublicProps
$attrs: Data
$refs: Data
$slots: Slots
@ -73,7 +141,7 @@ export type ComponentPublicInstance<
$parent: ComponentPublicInstance | null
$emit: EmitFn<E>
$el: any
$options: ComponentOptionsBase<P, B, D, C, M, E>
$options: Options
$forceUpdate: ReactiveEffect
$nextTick: typeof nextTick
$watch: typeof instanceWatch
@ -84,6 +152,12 @@ export type ComponentPublicInstance<
M &
ComponentCustomProperties
export type ComponentPublicInstanceConstructor<
T extends ComponentPublicInstance
> = {
new (): T
}
const publicPropertiesMap: Record<
string,
(i: ComponentInternalInstance) => any

View File

@ -0,0 +1,5 @@
export type UnionToIntersection<U> = (U extends any
? (k: U) => void
: never) extends ((k: infer I) => void)
? I
: never

View File

@ -23,6 +23,7 @@ describe('with object props', () => {
ddd: string[]
eee: () => { a: string }
fff: (a: number, b: string) => { a: boolean }
hhh: boolean
}
type GT = string & { __brand: unknown }
@ -67,6 +68,10 @@ describe('with object props', () => {
fff: {
type: Function as PropType<(a: number, b: string) => { a: boolean }>,
required: true
},
hhh: {
type: Boolean,
required: true
}
},
setup(props) {
@ -83,6 +88,7 @@ describe('with object props', () => {
expectType<ExpectedProps['ddd']>(props.ddd)
expectType<ExpectedProps['eee']>(props.eee)
expectType<ExpectedProps['fff']>(props.fff)
expectType<ExpectedProps['hhh']>(props.hhh)
// @ts-expect-error props should be readonly
expectError((props.a = 1))
@ -112,6 +118,7 @@ describe('with object props', () => {
expectType<ExpectedProps['ddd']>(props.ddd)
expectType<ExpectedProps['eee']>(props.eee)
expectType<ExpectedProps['fff']>(props.fff)
expectType<ExpectedProps['hhh']>(props.hhh)
// @ts-expect-error props should be readonly
expectError((props.a = 1))
@ -129,6 +136,7 @@ describe('with object props', () => {
expectType<ExpectedProps['ddd']>(this.ddd)
expectType<ExpectedProps['eee']>(this.eee)
expectType<ExpectedProps['fff']>(this.fff)
expectType<ExpectedProps['hhh']>(this.hhh)
// @ts-expect-error props on `this` should be readonly
expectError((this.a = 1))
@ -159,6 +167,7 @@ describe('with object props', () => {
ddd={['ddd']}
eee={() => ({ a: 'eee' })}
fff={(a, b) => ({ a: a > +b })}
hhh={false}
// should allow extraneous as attrs
class="bar"
// should allow key
@ -210,7 +219,7 @@ describe('with object props', () => {
// })
describe('type inference w/ array props declaration', () => {
defineComponent({
const MyComponent = defineComponent({
props: ['a', 'b'],
setup(props) {
// @ts-expect-error props should be readonly
@ -231,6 +240,9 @@ describe('type inference w/ array props declaration', () => {
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', () => {
@ -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', () => {
const comp = defineComponent({})
createApp(comp).mount('#hello')