diff --git a/packages/runtime-core/__tests__/mixins.spec.ts b/packages/runtime-core/__tests__/mixins.spec.ts index 408c0086d..0c0d48246 100644 --- a/packages/runtime-core/__tests__/mixins.spec.ts +++ b/packages/runtime-core/__tests__/mixins.spec.ts @@ -1,5 +1,6 @@ import { Component, ComponentClass, mixins } from '@vue/runtime-core' import { createInstance } from '@vue/runtime-test' +import { prop } from '@vue/decorators' const calls: string[] = [] @@ -9,10 +10,8 @@ beforeEach(() => { class ClassMixinA extends Component<{ p1: string }, { d11: number }> { // props - static props = { - p1: String - } - + @prop + p1: string // data d1 = 1 data() { @@ -23,7 +22,7 @@ class ClassMixinA extends Component<{ p1: string }, { d11: number }> { // computed get c1() { - return this.d1 + this.d11 + return this.d1 + this.$data.d11 } // lifecycle @@ -52,7 +51,7 @@ class ClassMixinB extends Component<{ p2: string }, { d21: number }> { } get c2() { - return this.d2 + this.d21 + return this.d2 + this.$data.d21 } // lifecycle @@ -197,15 +196,15 @@ describe('mixins', () => { // data expect(instance.d1).toBe(1) - expect(instance.d11).toBe(2) + expect(instance.$data.d11).toBe(2) expect(instance.d2).toBe(1) - expect(instance.d21).toBe(2) + expect(instance.$data.d21).toBe(2) expect(instance.d3).toBe(1) expect(instance.d31).toBe(2) // props expect(instance.p1).toBe('1') - expect(instance.p2).toBe('2') + expect(instance.$props.p2).toBe('2') expect(instance.p3).toBe('3') expect(instance.$props.p1).toBe('1') expect(instance.$props.p2).toBe('2') @@ -246,7 +245,7 @@ describe('mixins', () => { } get c3() { - return this.d3 + this.d31 + return this.d3 + this.$data.d31 } created() { @@ -278,7 +277,7 @@ describe('mixins', () => { } get c3() { - return this.d3 + this.d31 + return this.d3 + this.$data.d31 } created() { diff --git a/packages/runtime-core/src/component.ts b/packages/runtime-core/src/component.ts index 8e14e57a2..4e2aff245 100644 --- a/packages/runtime-core/src/component.ts +++ b/packages/runtime-core/src/component.ts @@ -14,6 +14,7 @@ import { ErrorTypes } from './errorHandling' import { initializeComponentInstance } from './componentInstance' import { EventEmitter, invokeListeners } from './optional/eventEmitter' import { warn } from './warning' +import { ComponentProxy } from './componentProxy' // public component instance type export interface Component

extends PublicInstanceMethods { @@ -30,7 +31,6 @@ export interface Component

extends PublicInstanceMethods { readonly $options: ComponentOptions readonly $refs: Record readonly $proxy: this - readonly $self: this } interface PublicInstanceMethods { @@ -97,10 +97,10 @@ export interface ComponentInstance

$props: P $attrs: Data $slots: Slots - $root: ComponentInstance - $children: ComponentInstance[] + $root: ComponentProxy + $children: ComponentProxy[] $options: ComponentOptions - $self: ComponentInstance // on proxies only + $proxy: ComponentProxy _update: ReactiveEffect _queueJob: ((fn: () => void) => void) @@ -119,13 +119,12 @@ class ComponentImplementation implements PublicInstanceMethods { $props: Data | null = null $attrs: Data | null = null $slots: Slots | null = null - $root: ComponentInstance | null = null - $parent: ComponentInstance | null = null - $children: ComponentInstance[] = [] + $root: ComponentProxy | null = null + $parent: ComponentProxy | null = null + $children: ComponentProxy[] = [] $options: ComponentOptions | null = null $refs: Record = {} - $proxy: any = null - $self: any + $proxy: ComponentProxy | null = null _rawData: Data | null = null _computedGetters: Record | null = null @@ -140,7 +139,11 @@ class ComponentImplementation implements PublicInstanceMethods { constructor(props?: object) { if (props === void 0) { - initializeComponentInstance(this as any) + // When invoked without any arguments, this is the default path where + // we initiailize a proper component instance. Note the returned value + // here is actually a proxy of the raw instance (and will be the `this` + // context) in all sub-class methods, including the constructor! + return initializeComponentInstance(this as any) as any } else { // the presence of the props argument indicates that this class is being // instantiated as a mixin, and should expose the props on itself diff --git a/packages/runtime-core/src/componentInstance.ts b/packages/runtime-core/src/componentInstance.ts index 51c6ed4b5..f0dc70f4d 100644 --- a/packages/runtime-core/src/componentInstance.ts +++ b/packages/runtime-core/src/componentInstance.ts @@ -1,10 +1,10 @@ import { VNode, MountedVNode } from './vdom' -import { Component, ComponentInstance, ComponentClass } from './component' +import { ComponentInstance, ComponentClass } from './component' import { initializeState } from './componentState' import { initializeProps } from './componentProps' import { initializeWatch, teardownWatch } from './componentWatch' import { initializeComputed, teardownComputed } from './componentComputed' -import { createRenderProxy } from './componentProxy' +import { ComponentProxy, createRenderProxy } from './componentProxy' import { resolveComponentOptionsFromClass } from './componentOptions' import { VNodeFlags } from './flags' import { ErrorTypes, callLifecycleHookWithHandler } from './errorHandling' @@ -14,25 +14,22 @@ import { EMPTY_OBJ } from '@vue/shared' let currentVNode: VNode | null = null let currentContextVNode: VNode | null = null -export function createComponentInstance( - vnode: VNode -): ComponentInstance { +export function createComponentInstance(vnode: VNode): ComponentInstance { // component instance creation is done in two steps. // first, `initializeComponentInstance` is called inside base component // constructor as the instance is created so that the extended component's - // constructor has access to certain properties and most importantly, - // this.$props. + // constructor has access to public properties and most importantly props. // we are storing the vnodes in variables here so that there's no need to // always pass args in super() currentVNode = vnode currentContextVNode = vnode.contextVNode const Component = vnode.tag as ComponentClass - const instance = (vnode.children = new Component() as ComponentInstance) + const instanceProxy = new Component() as ComponentProxy + const instance = instanceProxy._self // then we finish the initialization by collecting properties set on the // instance const { - $proxy, $options: { created, computed, watch } } = instance initializeState(instance, !Component.fromOptions) @@ -41,7 +38,7 @@ export function createComponentInstance( instance.$slots = currentVNode.slots || EMPTY_OBJ if (created) { - callLifecycleHookWithHandler(created, $proxy, ErrorTypes.CREATED) + callLifecycleHookWithHandler(created, instanceProxy, ErrorTypes.CREATED) } currentVNode = currentContextVNode = null @@ -50,8 +47,11 @@ export function createComponentInstance( // this is called inside the base component's constructor // it initializes all the way up to props so that they are available -// inside the extended component's constructor -export function initializeComponentInstance(instance: ComponentInstance) { +// inside the extended component's constructor, and returns the proxy of the +// raw instance. +export function initializeComponentInstance( + instance: T +): ComponentProxy { if (__DEV__ && currentVNode === null) { throw new Error( `Component classes are not meant to be manually instantiated.` @@ -88,10 +88,12 @@ export function initializeComponentInstance(instance: ComponentInstance) { callLifecycleHookWithHandler(beforeCreate, proxy, ErrorTypes.BEFORE_CREATE) } initializeProps(instance, props, (currentVNode as VNode).data) + + return proxy } export function teardownComponentInstance(instance: ComponentInstance) { - const parentComponent = instance.$parent && instance.$parent.$self + const parentComponent = instance.$parent && instance.$parent._self if (parentComponent && !parentComponent._unmounted) { parentComponent.$children.splice( parentComponent.$children.indexOf(instance.$proxy), diff --git a/packages/runtime-core/src/componentProps.ts b/packages/runtime-core/src/componentProps.ts index 044bfd754..ba800495d 100644 --- a/packages/runtime-core/src/componentProps.ts +++ b/packages/runtime-core/src/componentProps.ts @@ -44,13 +44,6 @@ export function initializeProps( ? immutable(attrs) : attrs : instance.$props - // expose initial props on the raw instance so that they can be accessed - // in the child class constructor by class field initializers. - if (options != null) { - // it's okay to just set it here because props options are normalized - // and reserved keys should have been filtered away - Object.assign(instance, props) - } } // resolve raw VNode data. diff --git a/packages/runtime-core/src/componentProxy.ts b/packages/runtime-core/src/componentProxy.ts index 40762e8eb..b65cfe235 100644 --- a/packages/runtime-core/src/componentProxy.ts +++ b/packages/runtime-core/src/componentProxy.ts @@ -22,7 +22,7 @@ function getBoundMethod(fn: Function, target: any, receiver: any): Function { const renderProxyHandlers = { get(target: ComponentInstance, key: string, receiver: any) { let i: any - if (key === '$self') { + if (key === '_self') { return target } else if ((i = target._rawData) !== null && i.hasOwnProperty(key)) { // data @@ -86,6 +86,11 @@ const renderProxyHandlers = { } } -export function createRenderProxy(instance: any): ComponentInstance { - return new Proxy(instance, renderProxyHandlers) as ComponentInstance +export type ComponentProxy = T & { _self: T } + +export function createRenderProxy( + instance: T +): ComponentProxy { + debugger + return new Proxy(instance, renderProxyHandlers) as any } diff --git a/packages/runtime-core/src/createRenderer.ts b/packages/runtime-core/src/createRenderer.ts index d92557cf5..cd7927d6d 100644 --- a/packages/runtime-core/src/createRenderer.ts +++ b/packages/runtime-core/src/createRenderer.ts @@ -1249,7 +1249,9 @@ export function createRenderer(options: RendererOptions) { // a vnode may already have an instance if this is a compat call with // new Vue() const instance = ((__COMPAT__ && vnode.children) || - createComponentInstance(vnode as any)) as ComponentInstance + (vnode.children = createComponentInstance( + vnode as any + ))) as ComponentInstance // inject platform-specific unmount to keep-alive container if ((vnode.tag as any)[KeepAliveSymbol] === true) { diff --git a/packages/runtime-core/src/errorHandling.ts b/packages/runtime-core/src/errorHandling.ts index 4e61b6016..2a0abd810 100644 --- a/packages/runtime-core/src/errorHandling.ts +++ b/packages/runtime-core/src/errorHandling.ts @@ -2,6 +2,7 @@ import { ComponentInstance } from './component' import { warn, pushWarningContext, popWarningContext } from './warning' import { VNode } from './vdom' import { VNodeFlags } from './flags' +import { ComponentProxy } from './componentProxy' export const enum ErrorTypes { BEFORE_CREATE = 1, @@ -48,7 +49,7 @@ const ErrorTypeStrings: Record = { export function callLifecycleHookWithHandler( hook: Function, - instanceProxy: ComponentInstance, + instanceProxy: ComponentProxy, type: ErrorTypes, arg?: any ) { @@ -56,11 +57,11 @@ export function callLifecycleHookWithHandler( const res = hook.call(instanceProxy, arg) if (res && !res._isVue && typeof res.then === 'function') { ;(res as Promise).catch(err => { - handleError(err, instanceProxy.$self, type) + handleError(err, instanceProxy._self, type) }) } } catch (err) { - handleError(err, instanceProxy.$self, type) + handleError(err, instanceProxy._self, type) } } @@ -85,10 +86,10 @@ export function handleError( cur = vnode.children as ComponentInstance } } else if (instance) { - cur = (instance as ComponentInstance).$parent + const parent = (instance as ComponentInstance).$parent + cur = parent && parent._self } while (cur) { - cur = cur.$self const handler = cur.errorCaptured if (handler) { try { @@ -103,7 +104,7 @@ export function handleError( logError(err2, ErrorTypes.ERROR_CAPTURED, contextVNode) } } - cur = cur.$parent + cur = cur.$parent && cur.$parent._self } logError(err, type, contextVNode) } @@ -118,7 +119,7 @@ function logError(err: Error, type: ErrorTypes, contextVNode: VNode | null) { warn( `Private fields cannot be accessed directly on \`this\` in a component ` + `class because they cannot be tunneled through Proxies. ` + - `Use \`this.$self.#field\` instead.` + `Use \`this._self.#field\` instead.` ) } else { warn(`Unhandled error${info ? ` ${info}` : ``}`) diff --git a/packages/vue-compat/src/index.ts b/packages/vue-compat/src/index.ts index edceebba1..d9cefee5f 100644 --- a/packages/vue-compat/src/index.ts +++ b/packages/vue-compat/src/index.ts @@ -17,7 +17,7 @@ class Vue { // convert it to a class const Component = createComponentClassFromOptions(options || {}) const vnode = h(Component) - const instance = createComponentInstance(vnode) + const instance = (vnode.children = createComponentInstance(vnode)) function mount(el: any) { const dom = typeof el === 'string' ? document.querySelector(el) : el @@ -26,10 +26,10 @@ class Vue { } if (options.el) { - return mount(options.el) + return mount(options.el) as any } else { ;(instance as any).$mount = mount - return instance.$proxy + return instance.$proxy as any } } } diff --git a/tsconfig.json b/tsconfig.json index 69423b402..ccb988349 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -26,7 +26,8 @@ "@vue/observer": ["packages/observer/src"], "@vue/scheduler": ["packages/scheduler/src"], "@vue/compiler-core": ["packages/compiler-core/src"], - "@vue/server-renderer": ["packages/server-renderer/src"] + "@vue/server-renderer": ["packages/server-renderer/src"], + "@vue/decorators": ["packages/decorators/src"] } }, "include": [