]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
refactor: return Proxy from base class constructor
authorEvan You <yyx990803@gmail.com>
Tue, 5 Mar 2019 21:24:07 +0000 (16:24 -0500)
committerEvan You <yyx990803@gmail.com>
Tue, 5 Mar 2019 21:24:07 +0000 (16:24 -0500)
packages/runtime-core/__tests__/mixins.spec.ts
packages/runtime-core/src/component.ts
packages/runtime-core/src/componentInstance.ts
packages/runtime-core/src/componentProps.ts
packages/runtime-core/src/componentProxy.ts
packages/runtime-core/src/createRenderer.ts
packages/runtime-core/src/errorHandling.ts
packages/vue-compat/src/index.ts
tsconfig.json

index 408c0086dd431222ce1a72fc65aeade179d2c370..0c0d48246bf24629ef3d0d0a6694c5313500054e 100644 (file)
@@ -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() {
index 8e14e57a238ec64d32acdf669d3d4d28a8103a7c..4e2aff2450550eac7e41645a310a6fa2d345af77 100644 (file)
@@ -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<P = {}, D = {}> extends PublicInstanceMethods {
@@ -30,7 +31,6 @@ export interface Component<P = {}, D = {}> extends PublicInstanceMethods {
   readonly $options: ComponentOptions<P, D, this>
   readonly $refs: Record<string | symbol, any>
   readonly $proxy: this
-  readonly $self: this
 }
 
 interface PublicInstanceMethods {
@@ -97,10 +97,10 @@ export interface ComponentInstance<P = {}, D = {}>
   $props: P
   $attrs: Data
   $slots: Slots
-  $root: ComponentInstance
-  $children: ComponentInstance[]
+  $root: ComponentProxy
+  $children: ComponentProxy[]
   $options: ComponentOptions<P, D>
-  $self: ComponentInstance<P, D> // on proxies only
+  $proxy: ComponentProxy<this>
 
   _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<string, ComponentInstance | RenderNode> = {}
-  $proxy: any = null
-  $self: any
+  $proxy: ComponentProxy<this> | null = null
 
   _rawData: Data | null = null
   _computedGetters: Record<string, ComputedGetter> | 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
index 51c6ed4b5ec92054d89bc30a9b681938369ebb4d..f0dc70f4d5f3cd17250a80d7377f64113eb08c40 100644 (file)
@@ -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<T extends Component>(
-  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<T extends Component>(
   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<T extends Component>(
 
 // 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<T extends ComponentInstance>(
+  instance: T
+): ComponentProxy<T> {
   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),
index 044bfd754f4b9dadc7b676df2909381c627910d2..ba800495d34cf23001a0811f23495b43cf3b5d0e 100644 (file)
@@ -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.
index 40762e8eb1c5ac04198d619634e66146111349ec..b65cfe235e7db79a6ec826a6e0e7ca2134f9387a 100644 (file)
@@ -22,7 +22,7 @@ function getBoundMethod(fn: Function, target: any, receiver: any): Function {
 const renderProxyHandlers = {
   get(target: ComponentInstance<any, any>, 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 = ComponentInstance> = T & { _self: T }
+
+export function createRenderProxy<T extends ComponentInstance>(
+  instance: T
+): ComponentProxy<T> {
+  debugger
+  return new Proxy(instance, renderProxyHandlers) as any
 }
index d92557cf5a61a1b9b5edd478bf31bc86875b01ce..cd7927d6d5bcf32f15f6560d96dc36568f1d6727 100644 (file)
@@ -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) {
index 4e61b60165fba94bad01891171fdb758344a5bb3..2a0abd8107ebf4f4b3cf451cee37818e3b204712 100644 (file)
@@ -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<number, string> = {
 
 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<any>).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}` : ``}`)
index edceebba19edb3642ee7b2de49bfe9aa1299b29e..d9cefee5fd5fe11baf2a4173f6b7ec7222164120 100644 (file)
@@ -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
     }
   }
 }
index 69423b402c405e2f007a045364d27555be4bbbfb..ccb988349d5663f17eb6c3b1a3fc2db626d77f73 100644 (file)
@@ -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": [