Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(runtime-core): mixins/extends support in TypeScript #626

Merged
merged 35 commits into from
Jun 9, 2020
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
c073dbf
feat(runtime-core): support mixins and extends for TS ThisType
dolymood Jan 14, 2020
7e32c2f
test(runtime-core): add mixins and extends
dolymood Jan 14, 2020
11f5b30
test(defineComponent): add mixins and extends
dolymood Jan 14, 2020
8485427
refactor: sync
dolymood Jan 14, 2020
058e6c7
fix(runtime-core): apiExtractor can not support typeof "options"
dolymood Jan 14, 2020
bfd35a1
fix(runtime-core): compatible with before h & defineComponent
dolymood Jan 16, 2020
fdfb31e
fix(runtime-core): h cases for setup
dolymood Jan 16, 2020
1f5bf5a
fix(runtime-core): h cases for ComponentOptionsBase
dolymood Jan 16, 2020
3b7971e
fix(runtime-core): withoutprops use union define - null
dolymood Jan 16, 2020
df580c0
fix(runtime-core): props options can be null #3b7971e
dolymood Jan 16, 2020
f603d3b
refactor(runtime-core): sync vue-next
dolymood Apr 30, 2020
604f163
Merge branch 'master' of https://github.com/vuejs/vue-next
dolymood Apr 30, 2020
d2250b9
test(types): add extends with mixins case
dolymood Apr 30, 2020
3251dcf
fix(runtime-core): remove PropMethod type useless code
dolymood Apr 30, 2020
3e0eb03
test(runtime-core): ensure mixin can access parent context
dolymood Apr 30, 2020
3193816
feat: update to typescript@3.9rc and type fixes
pikax May 1, 2020
11cd53d
types: fix defineComponent return type
pikax May 1, 2020
a9c8119
fix(runtime-core): merge from vue-next master
dolymood May 2, 2020
c0e302d
Merge branch 'master-github' into feat/ts3.9-rc_2
pikax May 3, 2020
42387b9
Revert "types: fix defineComponent return type"
pikax May 4, 2020
bf3e586
types: apply @ktsn :tada:
pikax May 4, 2020
59c23cb
Merge branch 'feat/ts3.9-rc_2' of https://github.com/pikax/vue-next
dolymood May 6, 2020
6ae01d5
fix(runtime-core): compatible with TS 3.9
dolymood May 7, 2020
9be67ac
Merge branch 'master-github' into feat/ts3.9-rc_2
pikax May 8, 2020
ced4603
test: removed dts and use tsc --noEmit, added built types testing
pikax May 8, 2020
93c958c
fix: Merge branch 'feat/ts3.9-rc_2' of https://github.com/pikax/vue-next
dolymood May 8, 2020
6e47393
fix(test-dts): add ts-expect-error
dolymood May 8, 2020
acebedf
chore: add readme
pikax May 8, 2020
e20da8b
Merge branch 'master-github' into feat/ts3.9-rc_2
pikax May 11, 2020
3aa35b8
chore: update typescript to 3.9.2
pikax May 13, 2020
4458c97
Merge branch 'master-github' into feat/ts3.9-rc_2
pikax May 13, 2020
ac4ec0a
chore: using @ts-ignore instead of `as any` on console.info
pikax May 13, 2020
526c9fd
build(deps): bump typescript 3.9.3
pikax May 20, 2020
962477c
refactor: sync pikax codes
dolymood Jun 1, 2020
0a0f303
Merge branch 'master' into master
yyx990803 Jun 9, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 100 additions & 21 deletions packages/runtime-core/__tests__/apiOptions.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -426,83 +426,117 @@ describe('api: options', () => {

test('mixins', () => {
const calls: string[] = []
const mixinA = {
const mixinA = defineComponent({
data() {
return {
a: 1
}
},
created(this: any) {
created() {
calls.push('mixinA created')
expect(this.a).toBe(1)
expect(this.b).toBe(2)
expect(this.c).toBe(3)
// should not visit other props/data... in one mixin
// ????? only test current mixin props/data
// expect(this.b).toBe(2)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test is quite important, because is expected you to access the parent props in the mixin.

But with typed mixins it makes quite hard to get those types using defineComponent(maybe another api defineMixin<vmProps>?).

I think this test would be better if you remove defineComponent from the mixins and call it in the Comp mixin

const mixinA = {
// ...
}

const mixinB = {
//...
}

// etc

const Comp = defineComponent({
  mixins: [
    defineComponent(mixinA),
    defineComponent(mixinB),
    defineComponent(mixinC)
  ],
// etc
)

Or having it has two tests, one defining typed based other using non-typed (no-defineComponent)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very good suggestion! 👍
Mixin types was tested in defineComponent-test-d.tsx for types cases.
Here we must ensure the runtime logic is correct.

// expect(this.c).toBe(3)
},
mounted() {
calls.push('mixinA mounted')
}
}
const mixinB = {
})
const mixinB = defineComponent({
props: {
bP: {
type: String
}
},
data() {
return {
b: 2
}
},
created(this: any) {
created() {
calls.push('mixinB created')
expect(this.a).toBe(1)
// expect(this.a).toBe(1)
expect(this.b).toBe(2)
expect(this.c).toBe(3)
expect(this.bP).toBeUndefined()
// expect(this.c).toBe(3)
},
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.a).toBe(1)
// expect(this.b).toBe(2)
// expect(this.bP).toBeUndefined()
expect(this.c).toBe(3)
expect(this.cP1).toBeUndefined()
},
mounted() {
calls.push('mixinC mounted')
}
})
const Comp = defineComponent({
mixins: [mixinA, 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'
])
})

test('extends', () => {
const calls: string[] = []
const Base = {
const Base = defineComponent({
data() {
return {
a: 1
}
},
methods: {
sayA() {}
},
mounted() {
calls.push('base')
}
}
const Comp = {
})
const Comp = defineComponent({
extends: Base,
data() {
return {
Expand All @@ -512,15 +546,60 @@ 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', () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as the previous component, we probably want to test the access of the component who's extending.

const calls: string[] = []
const Base = defineComponent({
data() {
return {
a: 1
}
},
methods: {
sayA() {}
},
mounted() {
calls.push('base')
}
})
const Base2 = defineComponent({
data() {
return {
b: true
}
},
mounted() {
calls.push('base2')
}
})
const Comp = defineComponent({
extends: Base,
mixins: [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() {
Expand Down
107 changes: 91 additions & 16 deletions packages/runtime-core/src/apiDefineComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@ import {
MethodOptions,
ComponentOptionsWithoutProps,
ComponentOptionsWithArrayProps,
ComponentOptionsWithObjectProps
ComponentOptionsWithObjectProps,
IComponentOptions
} from './componentOptions'
import { SetupContext, RenderFunction, 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'
Expand All @@ -24,17 +28,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)
Expand All @@ -45,21 +53,46 @@ export function defineComponent<
D = {},
C extends ComputedOptions = {},
M extends MethodOptions = {},
Mixin extends IComponentOptions = {},
Extends extends IComponentOptions = {},
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 }
Expand All @@ -70,6 +103,8 @@ export function defineComponent<
D,
C extends ComputedOptions = {},
M extends MethodOptions = {},
Mixin extends IComponentOptions = {},
Extends extends IComponentOptions = {},
E extends EmitsOptions = Record<string, any>,
EE extends string = string
>(
Expand All @@ -79,13 +114,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 contraints 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
Expand All @@ -97,6 +155,8 @@ export function defineComponent<
D,
C extends ComputedOptions = {},
M extends MethodOptions = {},
Mixin extends IComponentOptions = {},
Extends extends IComponentOptions = {},
E extends EmitsOptions = Record<string, any>,
EE extends string = string
>(
Expand All @@ -106,20 +166,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) {
Expand Down
Loading