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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user