]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
wip: improve options typing
authorEvan You <yyx990803@gmail.com>
Thu, 5 Sep 2019 20:09:30 +0000 (16:09 -0400)
committerEvan You <yyx990803@gmail.com>
Thu, 5 Sep 2019 20:09:30 +0000 (16:09 -0400)
packages/reactivity/src/computed.ts
packages/runtime-core/__tests__/apiCreateComponent.spec.tsx
packages/runtime-core/src/apiOptions.ts
packages/runtime-core/src/apiWatch.ts
packages/runtime-core/src/component.ts
tsconfig.json

index d795564404a85c63574a293bb198ae49cd7e44c8..5504ac79bbc57942de7a59f18de39fa8438bd590 100644 (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
 }
index a2775d4c54903c5e327f74e47b33cdf56d98dee8..991f826436e7087c14bf8f51b356d1d02f9993e5 100644 (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
+      }
+    }
+  })
+})
index 7ed2590a7b02712586c98d241c2d8edf3da20703..4c5b517e382068dd2cba2bc8051038a39e413c70 100644 (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) {
index 2b85b3838db14aa2da515dcf30964d91f396d342..3c14d915f7495b446639b11d5be6b6f205da2a83 100644 (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
 
index afd9a98a0e41bb51e64118124eb6cfaa484f1754..4a897ccdee1eeab24f5413ca080c5b0045bdc24f 100644 (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>
   >
 }
index b3b5f474e7339235ebb0b00dee15dddfc463c77b..46cb2306c702ac9426913c6a18f6a5379e2549a9 100644 (file)
@@ -10,6 +10,7 @@
     "noUnusedLocals": true,
     "strictNullChecks": true,
     "noImplicitAny": true,
+    "noImplicitThis": true,
     "experimentalDecorators": true,
     "esModuleInterop": true,
     "removeComments": false,