]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat: fix all cases for h and options type inference
authorEvan You <yyx990803@gmail.com>
Thu, 5 Sep 2019 22:48:49 +0000 (18:48 -0400)
committerEvan You <yyx990803@gmail.com>
Thu, 5 Sep 2019 22:48:49 +0000 (18:48 -0400)
packages/runtime-core/__tests__/apiApp.spec.ts
packages/runtime-core/__tests__/apiCreateComponent.spec.tsx
packages/runtime-core/__tests__/apiOptions.spec.ts
packages/runtime-core/__tests__/apiSetupContext.spec.ts
packages/runtime-core/src/apiOptions.ts
packages/runtime-core/src/apiReactivity.ts
packages/runtime-core/src/component.ts
packages/runtime-core/src/h.ts
packages/runtime-core/src/vnode.ts

index fa91792e614bd69f69219ed9fe614a96ef44dfdd..91ddfbb1ed70a0785940ea588083bc40cb9089b9 100644 (file)
@@ -23,8 +23,8 @@ describe('api: createApp', () => {
           default: 0
         }
       },
-      render() {
-        return this.count
+      setup(props: { count: number }) {
+        return () => props.count
       }
     }
 
index 991f826436e7087c14bf8f51b356d1d02f9993e5..f1ebdf54336d1302710d430bc2b1cf4212e84582 100644 (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)
     }
   })
 })
index 103a56a407d1044e56292d8500818ccb698bf418..2ba4bc3d57e0e60339df26b8905412ff2d59713b 100644 (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>`)
index 796347b7cfaa5677e2836fc0dd06503c6d1bed59..fd9c8ab7387fffc4c3f890aa8f150c6e729b3f44 100644 (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`)
   })
 
index 4c5b517e382068dd2cba2bc8051038a39e413c70..11f5df98f930fba548ae72de0fa089311c24f3b4 100644 (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
index 40757ae49003ec11c02e0b6e0dc0cb77094cb6d7..4749c2727ba2dc65a236a56be9cabda99d8b2cd8 100644 (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)
index 4a897ccdee1eeab24f5413ca080c5b0045bdc24f..2c24269255a1ee25f6f1231b098b5c17a3381954 100644 (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>
index c2ff1bed7d5a152cf7558a80a95d86b845173520..ca94b7c73b7c8c3a2e83c6c8cb160065bd0b0053 100644 (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> {
index 57b5b141177137d42caff8a194b1b1dfb2c16dcb..2bee91c109abd1f7bbe0b67f137db5f1bd2f81d1 100644 (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