wip: improve options typing

This commit is contained in:
Evan You 2019-09-05 16:09:30 -04:00
parent 369b9eb583
commit 9b90e673e8
6 changed files with 192 additions and 45 deletions

View File

@ -7,7 +7,7 @@ export interface ComputedRef<T> {
readonly effect: ReactiveEffect
}
export interface ComputedOptions<T = any> {
export interface ComputedOptions<T> {
get: () => T
set: (v: T) => void
}

View File

@ -1,6 +1,7 @@
import { createComponent } from '../src/component'
import { createComponent, ComponentRenderProxy } from '../src/component'
import { ref } from '@vue/reactivity'
import { PropType } from '../src/componentProps'
import { h } from '../src/h'
// mock React just for TSX testing purposes
const React = {
@ -55,6 +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)
}
})
// test TSX props inference
@ -71,9 +73,9 @@ test('type inference w/ optional props declaration', () => {
},
render() {
this.$props.msg
this.$data.a * 2
this.msg
this.a * 2
// return h('div', this.msg)
}
})
;(<Comp msg="hello"/>)
@ -99,7 +101,6 @@ test('type inference w/ array props declaration', () => {
render() {
this.$props.a
this.$props.b
this.$data.c
this.a
this.b
this.c
@ -107,3 +108,46 @@ test('type inference w/ array props declaration', () => {
})
;(<Comp a={1} b={2}/>)
})
test('with legacy options', () => {
createComponent({
props: { a: Number },
setup() {
return {
b: 123
}
},
data() {
this.a
this.b
return {
c: 234
}
},
computed: {
d(): number {
return this.b + 1
}
},
watch: {
a() {
this.b + 1
}
},
created() {
this.a && this.a * 2
this.b * 2
this.c * 2
this.d * 2
},
methods: {
doSomething() {
this.a && this.a * 2
this.b * 2
this.c * 2
this.d * 2
return (this.a || 0) + this.b + this.c + this.d
}
}
})
})

View File

@ -3,7 +3,8 @@ import {
Data,
ComponentOptions,
currentRenderingInstance,
currentInstance
currentInstance,
ComponentRenderProxy
} from './component'
import {
isFunction,
@ -15,8 +16,8 @@ import {
capitalize,
camelize
} from '@vue/shared'
import { computed, ComputedOptions } from './apiReactivity'
import { watch, WatchOptions } from './apiWatch'
import { computed } from './apiReactivity'
import { watch, WatchOptions, CleanupRegistrator } from './apiWatch'
import { provide, inject } from './apiInject'
import {
onBeforeMount,
@ -34,20 +35,61 @@ import { warn } from './warning'
// TODO legacy component definition also supports constructors with .options
type LegacyComponent = ComponentOptions
export interface ComputedOptions {
[key: string]:
| Function
| {
get: Function
set: Function
}
}
export interface MethodOptions {
[key: string]: Function
}
export type ExtracComputedReturns<T extends any> = {
[key in keyof T]: T[key] extends { get: Function }
? ReturnType<T[key]['get']>
: ReturnType<T[key]>
}
type WatchHandler = (
val: any,
oldVal: any,
onCleanup: CleanupRegistrator
) => void
// TODO type inference for these options
export interface LegacyOptions {
export interface LegacyOptions<
Props,
RawBindings,
D,
C extends ComputedOptions,
M extends MethodOptions,
ThisContext = ThisType<ComponentRenderProxy<Props, D, RawBindings, C, M>>
> {
el?: any
// state
data?: Data | (() => Data)
computed?: Record<string, (() => any) | ComputedOptions>
methods?: Record<string, Function>
data?:
| D
| (<This extends ComponentRenderProxy<Props, {}, RawBindings>>(
this: This
) => D)
computed?: C & ThisContext
methods?: M & ThisContext
// TODO watch array
watch?: Record<
string,
string | Function | { handler: Function } & WatchOptions
>
provide?: Data | (() => Data)
string | WatchHandler | { handler: WatchHandler } & WatchOptions
> &
ThisContext
provide?:
| Data
| (<This extends ComponentRenderProxy<Props, D, RawBindings, C, M>>(
this: This
) => any)
inject?:
| string[]
| Record<
@ -60,8 +102,10 @@ export interface LegacyOptions {
extends?: LegacyComponent
// lifecycle
beforeCreate?(): void
created?(): void
beforeCreate?(this: ComponentRenderProxy): void
created?<This extends ComponentRenderProxy<Props, D, RawBindings, C, M>>(
this: This
): void
beforeMount?(): void
mounted?(): void
beforeUpdate?(): void
@ -138,7 +182,7 @@ export function applyOptions(
}
if (computedOptions) {
for (const key in computedOptions) {
const opt = computedOptions[key]
const opt = (computedOptions as ComputedOptions)[key]
data[key] = isFunction(opt)
? computed(opt.bind(ctx))
: computed({
@ -149,7 +193,7 @@ export function applyOptions(
}
if (methods) {
for (const key in methods) {
data[key] = methods[key].bind(ctx)
data[key] = (methods as MethodOptions)[key].bind(ctx)
}
}
if (watchOptions) {

View File

@ -32,7 +32,7 @@ type MapSources<T> = {
[K in keyof T]: T[K] extends WatcherSource<infer V> ? V : never
}
type CleanupRegistrator = (invalidate: () => void) => void
export type CleanupRegistrator = (invalidate: () => void) => void
type SimpleEffect = (onCleanup: CleanupRegistrator) => void

View File

@ -22,14 +22,28 @@ import {
} from './errorHandling'
import { AppContext, createAppContext } from './apiApp'
import { Directive } from './directives'
import { applyOptions, LegacyOptions, resolveAsset } from './apiOptions'
import {
applyOptions,
LegacyOptions,
resolveAsset,
ComputedOptions,
MethodOptions,
ExtracComputedReturns
} from './apiOptions'
export type Data = { [key: string]: unknown }
// public properties exposed on the proxy, which is used as the render context
// in templates (as `this` in the render option)
export type ComponentRenderProxy<P = {}, S = {}, PublicProps = P> = {
$data: S
export type ComponentRenderProxy<
P = {},
D = {},
B = {},
C = {},
M = {},
PublicProps = P
> = {
$data: D
$props: PublicProps
$attrs: Data
$refs: Data
@ -38,50 +52,70 @@ export type ComponentRenderProxy<P = {}, S = {}, PublicProps = P> = {
$parent: ComponentInstance | null
$emit: (event: string, ...args: unknown[]) => void
} & P &
S
D &
UnwrapRef<B> &
ExtracComputedReturns<C> &
M
type RenderFunction<Props = {}, RawBindings = {}> = <
Bindings extends UnwrapRef<RawBindings>
type RenderFunction<P = {}, D = {}, B = {}, C = {}, M = {}> = <
This extends ComponentRenderProxy<P, D, B, C, M>
>(
this: ComponentRenderProxy<Props, Bindings>
this: This
) => VNodeChild
interface ComponentOptionsBase<Props, RawBindings> extends LegacyOptions {
interface ComponentOptionsBase<
Props,
RawBindings,
D,
C extends ComputedOptions,
M extends MethodOptions
> extends LegacyOptions<Props, RawBindings, D, C, M> {
setup?: (
props: Props,
ctx: SetupContext
) => RawBindings | (() => VNodeChild) | void
name?: string
template?: string
render?: RenderFunction<Props, RawBindings>
render?: RenderFunction<Props, D, RawBindings, C, M>
components?: Record<string, Component>
directives?: Record<string, Directive>
}
export interface ComponentOptionsWithoutProps<Props = {}, RawBindings = {}>
extends ComponentOptionsBase<Props, RawBindings> {
export interface ComponentOptionsWithoutProps<
Props = {},
RawBindings = {},
D = {},
C extends ComputedOptions = {},
M extends MethodOptions = {}
> extends ComponentOptionsBase<Props, RawBindings, D, C, M> {
props?: undefined
}
export interface ComponentOptionsWithArrayProps<
PropNames extends string = string,
RawBindings = {},
D = {},
C extends ComputedOptions = {},
M extends MethodOptions = {},
Props = { [key in PropNames]?: unknown }
> extends ComponentOptionsBase<Props, RawBindings> {
> extends ComponentOptionsBase<Props, RawBindings, D, C, M> {
props: PropNames[]
}
export interface ComponentOptionsWithProps<
PropsOptions = ComponentPropsOptions,
RawBindings = {},
D = {},
C extends ComputedOptions = {},
M extends MethodOptions = {},
Props = ExtractPropTypes<PropsOptions>
> extends ComponentOptionsBase<Props, RawBindings> {
> extends ComponentOptionsBase<Props, RawBindings, D, C, M> {
props: PropsOptions
}
export type ComponentOptions =
| ComponentOptionsWithProps
| ComponentOptionsWithoutProps
| ComponentOptionsWithProps
| ComponentOptionsWithArrayProps
export interface FunctionalComponent<P = {}> {
@ -116,7 +150,7 @@ interface SetupContext {
emit: ((event: string, ...args: unknown[]) => void)
}
export type ComponentInstance<P = Data, S = Data> = {
export type ComponentInstance<P = Data, D = Data> = {
type: FunctionalComponent | ComponentOptions
parent: ComponentInstance | null
appContext: AppContext
@ -125,7 +159,7 @@ export type ComponentInstance<P = Data, S = Data> = {
next: VNode | null
subTree: VNode
update: ReactiveEffect
render: RenderFunction<P, S> | null
render: RenderFunction<P, D> | null
effects: ReactiveEffect[] | null
provides: Data
@ -133,7 +167,7 @@ export type ComponentInstance<P = Data, S = Data> = {
directives: Record<string, Directive>
// the rest are only for stateful components
data: S
data: D
props: P
renderProxy: ComponentRenderProxy | null
propsProxy: P | null
@ -168,31 +202,55 @@ export function createComponent<Props>(
// overload 2: object format with no props
// (uses user defined props interface)
// return type is for Vetur and TSX support
export function createComponent<Props, RawBindings>(
options: ComponentOptionsWithoutProps<Props, RawBindings>
export function createComponent<
Props,
RawBindings,
D,
C extends ComputedOptions = {},
M extends MethodOptions = {}
>(
options: ComponentOptionsWithoutProps<Props, RawBindings, D, C, M>
): {
new (): ComponentRenderProxy<Props, UnwrapRef<RawBindings>>
new (): ComponentRenderProxy<Props, D, RawBindings, C, M>
}
// overload 3: object format with array props declaration
// props inferred as { [key in PropNames]?: unknown }
// return type is for Vetur and TSX support
export function createComponent<PropNames extends string, RawBindings>(
options: ComponentOptionsWithArrayProps<PropNames, RawBindings>
export function createComponent<
PropNames extends string,
RawBindings,
D,
C extends ComputedOptions = {},
M extends MethodOptions = {}
>(
options: ComponentOptionsWithArrayProps<PropNames, RawBindings, D, C, M>
): {
new (): ComponentRenderProxy<
{ [key in PropNames]?: unknown },
UnwrapRef<RawBindings>
D,
RawBindings,
C,
M
>
}
// overload 4: object format with object props declaration
// see `ExtractPropTypes` in ./componentProps.ts
export function createComponent<PropsOptions, RawBindings>(
options: ComponentOptionsWithProps<PropsOptions, RawBindings>
export function createComponent<
PropsOptions,
RawBindings,
D,
C extends ComputedOptions = {},
M extends MethodOptions = {}
>(
options: ComponentOptionsWithProps<PropsOptions, RawBindings, D, C, M>
): {
// for Vetur and TSX support
new (): ComponentRenderProxy<
ExtractPropTypes<PropsOptions>,
UnwrapRef<RawBindings>,
D,
RawBindings,
C,
M,
ExtractPropTypes<PropsOptions, false>
>
}

View File

@ -10,6 +10,7 @@
"noUnusedLocals": true,
"strictNullChecks": true,
"noImplicitAny": true,
"noImplicitThis": true,
"experimentalDecorators": true,
"esModuleInterop": true,
"removeComments": false,