feat: fix all cases for h and options type inference

This commit is contained in:
Evan You 2019-09-05 18:48:49 -04:00
parent 6c7cbb0dc9
commit 94a05561f8
9 changed files with 83 additions and 85 deletions

View File

@ -23,8 +23,8 @@ describe('api: createApp', () => {
default: 0
}
},
render() {
return this.count
setup(props: { count: number }) {
return () => props.count
}
}

View File

@ -1,4 +1,4 @@
import { createComponent, ComponentRenderProxy } from '../src/component'
import { createComponent } from '../src/component'
import { ref } from '@vue/reactivity'
import { PropType } from '../src/componentProps'
import { h } from '../src/h'
@ -56,7 +56,7 @@ test('createComponent type inference', () => {
this.d.e.slice()
this.cc && this.cc.push('hoo')
this.dd.push('dd')
// return h('div', this.bb)
return h('div', this.bb)
}
})
// test TSX props inference
@ -75,7 +75,7 @@ test('type inference w/ optional props declaration', () => {
this.$props.msg
this.msg
this.a * 2
// return h('div', this.msg)
return h('div', this.msg)
}
})
;(<Comp msg="hello"/>)
@ -118,10 +118,10 @@ test('with legacy options', () => {
}
},
data() {
this.a
this.b
// Limitation: we cannot expose the return result of setup() on `this`
// here in data() - somehow that would mess up the inference
return {
c: 234
c: this.a || 123
}
},
computed: {
@ -148,6 +148,13 @@ test('with legacy options', () => {
this.d * 2
return (this.a || 0) + this.b + this.c + this.d
}
},
render() {
this.a && this.a * 2
this.b * 2
this.c * 2
this.d * 2
return h('div', (this.a || 0) + this.b + this.c + this.d)
}
})
})

View File

@ -7,12 +7,13 @@ import {
TestElement,
nextTick,
renderToString,
ref
ref,
createComponent
} from '@vue/runtime-test'
describe('api: options', () => {
test('data', async () => {
const Comp = {
const Comp = createComponent({
data() {
return {
foo: 1
@ -29,7 +30,7 @@ describe('api: options', () => {
this.foo
)
}
}
})
const root = nodeOps.createElement('div')
render(h(Comp), root)
expect(serializeInner(root)).toBe(`<div>1</div>`)
@ -40,17 +41,17 @@ describe('api: options', () => {
})
test('computed', async () => {
const Comp = {
const Comp = createComponent({
data() {
return {
foo: 1
}
},
computed: {
bar() {
bar(): number {
return this.foo + 1
},
baz() {
baz(): number {
return this.bar + 1
}
},
@ -65,7 +66,7 @@ describe('api: options', () => {
this.bar + this.baz
)
}
}
})
const root = nodeOps.createElement('div')
render(h(Comp), root)
expect(serializeInner(root)).toBe(`<div>5</div>`)
@ -76,7 +77,7 @@ describe('api: options', () => {
})
test('methods', async () => {
const Comp = {
const Comp = createComponent({
data() {
return {
foo: 1
@ -96,7 +97,7 @@ describe('api: options', () => {
this.foo
)
}
}
})
const root = nodeOps.createElement('div')
render(h(Comp), root)
expect(serializeInner(root)).toBe(`<div>1</div>`)
@ -107,7 +108,7 @@ describe('api: options', () => {
})
test('watch', async () => {
function returnThis() {
function returnThis(this: any) {
return this
}
const spyA = jest.fn(returnThis)
@ -188,20 +189,20 @@ describe('api: options', () => {
render() {
return [h(ChildA), h(ChildB), h(ChildC), h(ChildD)]
}
}
} as any
const ChildA = {
inject: ['a'],
render() {
return this.a
}
}
} as any
const ChildB = {
// object alias
inject: { b: 'a' },
render() {
return this.b
}
}
} as any
const ChildC = {
inject: {
b: {
@ -211,7 +212,7 @@ describe('api: options', () => {
render() {
return this.b
}
}
} as any
const ChildD = {
inject: {
b: {
@ -222,7 +223,7 @@ describe('api: options', () => {
render() {
return this.b
}
}
} as any
expect(renderToString(h(Root))).toBe(`<!---->1112<!---->`)
})
@ -287,7 +288,7 @@ describe('api: options', () => {
unmounted() {
calls.push('mid onUnmounted')
},
render() {
render(this: any) {
return h(Child, { count: this.$props.count })
}
}
@ -317,7 +318,7 @@ describe('api: options', () => {
unmounted() {
calls.push('child onUnmounted')
},
render() {
render(this: any) {
return h('div', this.$props.count)
}
}
@ -375,7 +376,7 @@ describe('api: options', () => {
a: 1
}
},
created() {
created(this: any) {
calls.push('mixinA created')
expect(this.a).toBe(1)
expect(this.b).toBe(2)
@ -391,7 +392,7 @@ describe('api: options', () => {
b: 2
}
},
created() {
created(this: any) {
calls.push('mixinB created')
expect(this.a).toBe(1)
expect(this.b).toBe(2)
@ -408,7 +409,7 @@ describe('api: options', () => {
c: 3
}
},
created() {
created(this: any) {
calls.push('comp created')
expect(this.a).toBe(1)
expect(this.b).toBe(2)
@ -417,7 +418,7 @@ describe('api: options', () => {
mounted() {
calls.push('comp mounted')
},
render() {
render(this: any) {
return `${this.a}${this.b}${this.c}`
}
}
@ -455,7 +456,7 @@ describe('api: options', () => {
mounted() {
calls.push('comp')
},
render() {
render(this: any) {
return `${this.a}${this.b}`
}
}
@ -465,7 +466,7 @@ describe('api: options', () => {
})
test('accessing setup() state from options', async () => {
const Comp = {
const Comp = createComponent({
setup() {
return {
count: ref(0)
@ -473,11 +474,11 @@ describe('api: options', () => {
},
data() {
return {
plusOne: this.count + 1
plusOne: (this as any).count + 1
}
},
computed: {
plusTwo() {
plusTwo(): number {
return this.count + 2
}
},
@ -495,7 +496,7 @@ describe('api: options', () => {
`${this.count},${this.plusOne},${this.plusTwo}`
)
}
}
})
const root = nodeOps.createElement('div')
render(h(Comp), root)
expect(serializeInner(root)).toBe(`<div>0,1,2</div>`)

View File

@ -16,7 +16,7 @@ import {
describe('api: setup context', () => {
it('should expose return values to template render context', () => {
const Comp = {
const Comp = createComponent({
setup() {
return {
// ref should auto-unwrap
@ -30,7 +30,7 @@ describe('api: setup context', () => {
render() {
return `${this.ref} ${this.object.msg} ${this.value}`
}
}
})
expect(renderToString(h(Comp))).toMatch(`foo bar baz`)
})

View File

@ -66,30 +66,20 @@ export interface LegacyOptions<
RawBindings,
D,
C extends ComputedOptions,
M extends MethodOptions,
ThisContext = ThisType<ComponentRenderProxy<Props, D, RawBindings, C, M>>
M extends MethodOptions
> {
el?: any
// state
data?:
| D
| (<This extends ComponentRenderProxy<Props, {}, RawBindings>>(
this: This
) => D)
computed?: C & ThisContext
methods?: M & ThisContext
data?: D | ((this: ComponentRenderProxy<Props>) => D)
computed?: C
methods?: M
// TODO watch array
watch?: Record<
string,
string | WatchHandler | { handler: WatchHandler } & WatchOptions
> &
ThisContext
provide?:
| Data
| (<This extends ComponentRenderProxy<Props, D, RawBindings, C, M>>(
this: This
) => any)
>
provide?: Data | Function
inject?:
| string[]
| Record<
@ -102,10 +92,8 @@ export interface LegacyOptions<
extends?: LegacyComponent
// lifecycle
beforeCreate?(this: ComponentRenderProxy): void
created?<This extends ComponentRenderProxy<Props, D, RawBindings, C, M>>(
this: This
): void
beforeCreate?(): void
created?(): void
beforeMount?(): void
mounted?(): void
beforeUpdate?(): void

View File

@ -18,14 +18,14 @@ export {
Ref,
ComputedRef,
UnwrapRef,
ComputedOptions
WritableComputedOptions
} from '@vue/reactivity'
import {
Ref,
computed as _computed,
ComputedRef,
ComputedOptions,
WritableComputedOptions,
ReactiveEffect
} from '@vue/reactivity'
@ -40,7 +40,7 @@ export function recordEffect(effect: ReactiveEffect) {
}
export function computed<T>(getter: () => T): ComputedRef<T>
export function computed<T>(options: ComputedOptions<T>): Ref<T>
export function computed<T>(options: WritableComputedOptions<T>): Ref<T>
export function computed<T>(getterOrOptions: any) {
const c = _computed(getterOrOptions)
recordEffect(c.effect)

View File

@ -37,8 +37,8 @@ export type Data = { [key: string]: unknown }
// in templates (as `this` in the render option)
export type ComponentRenderProxy<
P = {},
D = {},
B = {},
D = {},
C = {},
M = {},
PublicProps = P
@ -52,17 +52,11 @@ export type ComponentRenderProxy<
$parent: ComponentInstance | null
$emit: (event: string, ...args: unknown[]) => void
} & P &
D &
UnwrapRef<B> &
D &
ExtracComputedReturns<C> &
M
type RenderFunction<P = {}, D = {}, B = {}, C = {}, M = {}> = <
This extends ComponentRenderProxy<P, D, B, C, M>
>(
this: This
) => VNodeChild
interface ComponentOptionsBase<
Props,
RawBindings,
@ -71,47 +65,53 @@ interface ComponentOptionsBase<
M extends MethodOptions
> extends LegacyOptions<Props, RawBindings, D, C, M> {
setup?: (
this: null,
props: Props,
ctx: SetupContext
) => RawBindings | (() => VNodeChild) | void
name?: string
template?: string
render?: RenderFunction<Props, D, RawBindings, C, M>
// Note: we are intentionally using the signature-less `Function` type here
// since any type with signature will cause the whole inference to fail when
// the return expression contains reference to `this`.
// Luckily `render()` doesn't need any arguments nor does it care about return
// type.
render?: Function
components?: Record<string, Component>
directives?: Record<string, Directive>
}
export interface ComponentOptionsWithoutProps<
export type ComponentOptionsWithoutProps<
Props = {},
RawBindings = {},
D = {},
C extends ComputedOptions = {},
M extends MethodOptions = {}
> extends ComponentOptionsBase<Props, RawBindings, D, C, M> {
> = ComponentOptionsBase<Props, RawBindings, D, C, M> & {
props?: undefined
}
} & ThisType<ComponentRenderProxy<Props, RawBindings, D, C, M>>
export interface ComponentOptionsWithArrayProps<
export type ComponentOptionsWithArrayProps<
PropNames extends string = string,
RawBindings = {},
D = {},
C extends ComputedOptions = {},
M extends MethodOptions = {},
Props = { [key in PropNames]?: unknown }
> extends ComponentOptionsBase<Props, RawBindings, D, C, M> {
> = ComponentOptionsBase<Props, RawBindings, D, C, M> & {
props: PropNames[]
}
} & ThisType<ComponentRenderProxy<Props, RawBindings, D, C, M>>
export interface ComponentOptionsWithProps<
export type ComponentOptionsWithProps<
PropsOptions = ComponentPropsOptions,
RawBindings = {},
D = {},
C extends ComputedOptions = {},
M extends MethodOptions = {},
Props = ExtractPropTypes<PropsOptions>
> extends ComponentOptionsBase<Props, RawBindings, D, C, M> {
> = ComponentOptionsBase<Props, RawBindings, D, C, M> & {
props: PropsOptions
}
} & ThisType<ComponentRenderProxy<Props, RawBindings, D, C, M>>
export type ComponentOptions =
| ComponentOptionsWithoutProps
@ -150,6 +150,8 @@ interface SetupContext {
emit: ((event: string, ...args: unknown[]) => void)
}
type RenderFunction = () => VNodeChild
export type ComponentInstance<P = Data, D = Data> = {
type: FunctionalComponent | ComponentOptions
parent: ComponentInstance | null
@ -159,7 +161,7 @@ export type ComponentInstance<P = Data, D = Data> = {
next: VNode | null
subTree: VNode
update: ReactiveEffect
render: RenderFunction<P, D> | null
render: RenderFunction | null
effects: ReactiveEffect[] | null
provides: Data
@ -211,7 +213,7 @@ export function createComponent<
>(
options: ComponentOptionsWithoutProps<Props, RawBindings, D, C, M>
): {
new (): ComponentRenderProxy<Props, D, RawBindings, C, M>
new (): ComponentRenderProxy<Props, RawBindings, D, C, M>
}
// overload 3: object format with array props declaration
// props inferred as { [key in PropNames]?: unknown }
@ -227,8 +229,8 @@ export function createComponent<
): {
new (): ComponentRenderProxy<
{ [key in PropNames]?: unknown },
D,
RawBindings,
D,
C,
M
>
@ -247,8 +249,8 @@ export function createComponent<
// for Vetur and TSX support
new (): ComponentRenderProxy<
ExtractPropTypes<PropsOptions>,
D,
RawBindings,
D,
C,
M,
ExtractPropTypes<PropsOptions, false>

View File

@ -11,10 +11,10 @@ import { Ref } from '@vue/reactivity'
import { RawSlots } from './componentSlots'
import {
FunctionalComponent,
ComponentOptions,
ComponentOptionsWithoutProps,
ComponentOptionsWithArrayProps,
ComponentOptionsWithProps
ComponentOptionsWithProps,
ComponentOptions
} from './component'
import { ExtractPropTypes } from './componentProps'
@ -57,7 +57,7 @@ interface Props {
[Symbol.iterator]?: never
}
type Children = string | number | VNodeChildren
type Children = string | number | boolean | VNodeChildren | (() => any)
// fake constructor type returned from `createComponent`
interface Constructor<P = any> {

View File

@ -28,7 +28,7 @@ export type VNodeTypes =
| typeof Text
| typeof Empty
type VNodeChildAtom = VNode | string | number | null | void
type VNodeChildAtom = VNode | string | number | boolean | null | void
export interface VNodeChildren extends Array<VNodeChildren | VNodeChildAtom> {}
export type VNodeChild = VNodeChildAtom | VNodeChildren