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": [