refactor: return Proxy from base class constructor
This commit is contained in:
		
							parent
							
								
									c335939dcf
								
							
						
					
					
						commit
						ec0ccd2337
					
				| @ -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() { | ||||
|  | ||||
| @ -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
 | ||||
|  | ||||
| @ -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), | ||||
|  | ||||
| @ -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.
 | ||||
|  | ||||
| @ -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 | ||||
| } | ||||
|  | ||||
| @ -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) { | ||||
|  | ||||
| @ -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}` : ``}`) | ||||
|  | ||||
| @ -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 | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -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": [ | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user