diff --git a/packages/runtime-core/__tests__/apiApp.spec.ts b/packages/runtime-core/__tests__/apiApp.spec.ts index fa91792e6..91ddfbb1e 100644 --- a/packages/runtime-core/__tests__/apiApp.spec.ts +++ b/packages/runtime-core/__tests__/apiApp.spec.ts @@ -23,8 +23,8 @@ describe('api: createApp', () => { default: 0 } }, - render() { - return this.count + setup(props: { count: number }) { + return () => props.count } } diff --git a/packages/runtime-core/__tests__/apiCreateComponent.spec.tsx b/packages/runtime-core/__tests__/apiCreateComponent.spec.tsx index 991f82643..f1ebdf543 100644 --- a/packages/runtime-core/__tests__/apiCreateComponent.spec.tsx +++ b/packages/runtime-core/__tests__/apiCreateComponent.spec.tsx @@ -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) } }) ;() @@ -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) } }) }) diff --git a/packages/runtime-core/__tests__/apiOptions.spec.ts b/packages/runtime-core/__tests__/apiOptions.spec.ts index 103a56a40..2ba4bc3d5 100644 --- a/packages/runtime-core/__tests__/apiOptions.spec.ts +++ b/packages/runtime-core/__tests__/apiOptions.spec.ts @@ -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(`
1
`) @@ -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(`
5
`) @@ -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(`
1
`) @@ -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(`
0,1,2
`) diff --git a/packages/runtime-core/__tests__/apiSetupContext.spec.ts b/packages/runtime-core/__tests__/apiSetupContext.spec.ts index 796347b7c..fd9c8ab73 100644 --- a/packages/runtime-core/__tests__/apiSetupContext.spec.ts +++ b/packages/runtime-core/__tests__/apiSetupContext.spec.ts @@ -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`) }) diff --git a/packages/runtime-core/src/apiOptions.ts b/packages/runtime-core/src/apiOptions.ts index 4c5b517e3..11f5df98f 100644 --- a/packages/runtime-core/src/apiOptions.ts +++ b/packages/runtime-core/src/apiOptions.ts @@ -66,30 +66,20 @@ export interface LegacyOptions< RawBindings, D, C extends ComputedOptions, - M extends MethodOptions, - ThisContext = ThisType> + M extends MethodOptions > { el?: any // state - data?: - | D - | (>( - this: This - ) => D) - computed?: C & ThisContext - methods?: M & ThisContext + data?: D | ((this: ComponentRenderProxy) => D) + computed?: C + methods?: M // TODO watch array watch?: Record< string, string | WatchHandler | { handler: WatchHandler } & WatchOptions - > & - ThisContext - provide?: - | Data - | (>( - 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: This - ): void + beforeCreate?(): void + created?(): void beforeMount?(): void mounted?(): void beforeUpdate?(): void diff --git a/packages/runtime-core/src/apiReactivity.ts b/packages/runtime-core/src/apiReactivity.ts index 40757ae49..4749c2727 100644 --- a/packages/runtime-core/src/apiReactivity.ts +++ b/packages/runtime-core/src/apiReactivity.ts @@ -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(getter: () => T): ComputedRef -export function computed(options: ComputedOptions): Ref +export function computed(options: WritableComputedOptions): Ref export function computed(getterOrOptions: any) { const c = _computed(getterOrOptions) recordEffect(c.effect) diff --git a/packages/runtime-core/src/component.ts b/packages/runtime-core/src/component.ts index 4a897ccde..2c2426925 100644 --- a/packages/runtime-core/src/component.ts +++ b/packages/runtime-core/src/component.ts @@ -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 & + D & ExtracComputedReturns & M -type RenderFunction

= < - This extends ComponentRenderProxy ->( - this: This -) => VNodeChild - interface ComponentOptionsBase< Props, RawBindings, @@ -71,47 +65,53 @@ interface ComponentOptionsBase< M extends MethodOptions > extends LegacyOptions { setup?: ( + this: null, props: Props, ctx: SetupContext ) => RawBindings | (() => VNodeChild) | void name?: string template?: string - render?: RenderFunction + // 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 directives?: Record } -export interface ComponentOptionsWithoutProps< +export type ComponentOptionsWithoutProps< Props = {}, RawBindings = {}, D = {}, C extends ComputedOptions = {}, M extends MethodOptions = {} -> extends ComponentOptionsBase { +> = ComponentOptionsBase & { props?: undefined -} +} & ThisType> -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 { +> = ComponentOptionsBase & { props: PropNames[] -} +} & ThisType> -export interface ComponentOptionsWithProps< +export type ComponentOptionsWithProps< PropsOptions = ComponentPropsOptions, RawBindings = {}, D = {}, C extends ComputedOptions = {}, M extends MethodOptions = {}, Props = ExtractPropTypes -> extends ComponentOptionsBase { +> = ComponentOptionsBase & { props: PropsOptions -} +} & ThisType> export type ComponentOptions = | ComponentOptionsWithoutProps @@ -150,6 +150,8 @@ interface SetupContext { emit: ((event: string, ...args: unknown[]) => void) } +type RenderFunction = () => VNodeChild + export type ComponentInstance

= { type: FunctionalComponent | ComponentOptions parent: ComponentInstance | null @@ -159,7 +161,7 @@ export type ComponentInstance

= { next: VNode | null subTree: VNode update: ReactiveEffect - render: RenderFunction | null + render: RenderFunction | null effects: ReactiveEffect[] | null provides: Data @@ -211,7 +213,7 @@ export function createComponent< >( options: ComponentOptionsWithoutProps ): { - new (): ComponentRenderProxy + new (): ComponentRenderProxy } // 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, - D, RawBindings, + D, C, M, ExtractPropTypes diff --git a/packages/runtime-core/src/h.ts b/packages/runtime-core/src/h.ts index c2ff1bed7..ca94b7c73 100644 --- a/packages/runtime-core/src/h.ts +++ b/packages/runtime-core/src/h.ts @@ -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

{ diff --git a/packages/runtime-core/src/vnode.ts b/packages/runtime-core/src/vnode.ts index 57b5b1411..2bee91c10 100644 --- a/packages/runtime-core/src/vnode.ts +++ b/packages/runtime-core/src/vnode.ts @@ -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 {} export type VNodeChild = VNodeChildAtom | VNodeChildren