wip: improve options typing
This commit is contained in:
parent
369b9eb583
commit
9b90e673e8
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
||||
|
@ -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>
|
||||
>
|
||||
}
|
||||
|
@ -10,6 +10,7 @@
|
||||
"noUnusedLocals": true,
|
||||
"strictNullChecks": true,
|
||||
"noImplicitAny": true,
|
||||
"noImplicitThis": true,
|
||||
"experimentalDecorators": true,
|
||||
"esModuleInterop": true,
|
||||
"removeComments": false,
|
||||
|
Loading…
Reference in New Issue
Block a user