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 { Component, ComponentClass, mixins } from '@vue/runtime-core'
|
||||||
import { createInstance } from '@vue/runtime-test'
|
import { createInstance } from '@vue/runtime-test'
|
||||||
|
import { prop } from '@vue/decorators'
|
||||||
|
|
||||||
const calls: string[] = []
|
const calls: string[] = []
|
||||||
|
|
||||||
@ -9,10 +10,8 @@ beforeEach(() => {
|
|||||||
|
|
||||||
class ClassMixinA extends Component<{ p1: string }, { d11: number }> {
|
class ClassMixinA extends Component<{ p1: string }, { d11: number }> {
|
||||||
// props
|
// props
|
||||||
static props = {
|
@prop
|
||||||
p1: String
|
p1: string
|
||||||
}
|
|
||||||
|
|
||||||
// data
|
// data
|
||||||
d1 = 1
|
d1 = 1
|
||||||
data() {
|
data() {
|
||||||
@ -23,7 +22,7 @@ class ClassMixinA extends Component<{ p1: string }, { d11: number }> {
|
|||||||
|
|
||||||
// computed
|
// computed
|
||||||
get c1() {
|
get c1() {
|
||||||
return this.d1 + this.d11
|
return this.d1 + this.$data.d11
|
||||||
}
|
}
|
||||||
|
|
||||||
// lifecycle
|
// lifecycle
|
||||||
@ -52,7 +51,7 @@ class ClassMixinB extends Component<{ p2: string }, { d21: number }> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get c2() {
|
get c2() {
|
||||||
return this.d2 + this.d21
|
return this.d2 + this.$data.d21
|
||||||
}
|
}
|
||||||
|
|
||||||
// lifecycle
|
// lifecycle
|
||||||
@ -197,15 +196,15 @@ describe('mixins', () => {
|
|||||||
|
|
||||||
// data
|
// data
|
||||||
expect(instance.d1).toBe(1)
|
expect(instance.d1).toBe(1)
|
||||||
expect(instance.d11).toBe(2)
|
expect(instance.$data.d11).toBe(2)
|
||||||
expect(instance.d2).toBe(1)
|
expect(instance.d2).toBe(1)
|
||||||
expect(instance.d21).toBe(2)
|
expect(instance.$data.d21).toBe(2)
|
||||||
expect(instance.d3).toBe(1)
|
expect(instance.d3).toBe(1)
|
||||||
expect(instance.d31).toBe(2)
|
expect(instance.d31).toBe(2)
|
||||||
|
|
||||||
// props
|
// props
|
||||||
expect(instance.p1).toBe('1')
|
expect(instance.p1).toBe('1')
|
||||||
expect(instance.p2).toBe('2')
|
expect(instance.$props.p2).toBe('2')
|
||||||
expect(instance.p3).toBe('3')
|
expect(instance.p3).toBe('3')
|
||||||
expect(instance.$props.p1).toBe('1')
|
expect(instance.$props.p1).toBe('1')
|
||||||
expect(instance.$props.p2).toBe('2')
|
expect(instance.$props.p2).toBe('2')
|
||||||
@ -246,7 +245,7 @@ describe('mixins', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get c3() {
|
get c3() {
|
||||||
return this.d3 + this.d31
|
return this.d3 + this.$data.d31
|
||||||
}
|
}
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
@ -278,7 +277,7 @@ describe('mixins', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get c3() {
|
get c3() {
|
||||||
return this.d3 + this.d31
|
return this.d3 + this.$data.d31
|
||||||
}
|
}
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
|
@ -14,6 +14,7 @@ import { ErrorTypes } from './errorHandling'
|
|||||||
import { initializeComponentInstance } from './componentInstance'
|
import { initializeComponentInstance } from './componentInstance'
|
||||||
import { EventEmitter, invokeListeners } from './optional/eventEmitter'
|
import { EventEmitter, invokeListeners } from './optional/eventEmitter'
|
||||||
import { warn } from './warning'
|
import { warn } from './warning'
|
||||||
|
import { ComponentProxy } from './componentProxy'
|
||||||
|
|
||||||
// public component instance type
|
// public component instance type
|
||||||
export interface Component<P = {}, D = {}> extends PublicInstanceMethods {
|
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 $options: ComponentOptions<P, D, this>
|
||||||
readonly $refs: Record<string | symbol, any>
|
readonly $refs: Record<string | symbol, any>
|
||||||
readonly $proxy: this
|
readonly $proxy: this
|
||||||
readonly $self: this
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PublicInstanceMethods {
|
interface PublicInstanceMethods {
|
||||||
@ -97,10 +97,10 @@ export interface ComponentInstance<P = {}, D = {}>
|
|||||||
$props: P
|
$props: P
|
||||||
$attrs: Data
|
$attrs: Data
|
||||||
$slots: Slots
|
$slots: Slots
|
||||||
$root: ComponentInstance
|
$root: ComponentProxy
|
||||||
$children: ComponentInstance[]
|
$children: ComponentProxy[]
|
||||||
$options: ComponentOptions<P, D>
|
$options: ComponentOptions<P, D>
|
||||||
$self: ComponentInstance<P, D> // on proxies only
|
$proxy: ComponentProxy<this>
|
||||||
|
|
||||||
_update: ReactiveEffect
|
_update: ReactiveEffect
|
||||||
_queueJob: ((fn: () => void) => void)
|
_queueJob: ((fn: () => void) => void)
|
||||||
@ -119,13 +119,12 @@ class ComponentImplementation implements PublicInstanceMethods {
|
|||||||
$props: Data | null = null
|
$props: Data | null = null
|
||||||
$attrs: Data | null = null
|
$attrs: Data | null = null
|
||||||
$slots: Slots | null = null
|
$slots: Slots | null = null
|
||||||
$root: ComponentInstance | null = null
|
$root: ComponentProxy | null = null
|
||||||
$parent: ComponentInstance | null = null
|
$parent: ComponentProxy | null = null
|
||||||
$children: ComponentInstance[] = []
|
$children: ComponentProxy[] = []
|
||||||
$options: ComponentOptions | null = null
|
$options: ComponentOptions | null = null
|
||||||
$refs: Record<string, ComponentInstance | RenderNode> = {}
|
$refs: Record<string, ComponentInstance | RenderNode> = {}
|
||||||
$proxy: any = null
|
$proxy: ComponentProxy<this> | null = null
|
||||||
$self: any
|
|
||||||
|
|
||||||
_rawData: Data | null = null
|
_rawData: Data | null = null
|
||||||
_computedGetters: Record<string, ComputedGetter> | null = null
|
_computedGetters: Record<string, ComputedGetter> | null = null
|
||||||
@ -140,7 +139,11 @@ class ComponentImplementation implements PublicInstanceMethods {
|
|||||||
|
|
||||||
constructor(props?: object) {
|
constructor(props?: object) {
|
||||||
if (props === void 0) {
|
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 {
|
} else {
|
||||||
// the presence of the props argument indicates that this class is being
|
// the presence of the props argument indicates that this class is being
|
||||||
// instantiated as a mixin, and should expose the props on itself
|
// instantiated as a mixin, and should expose the props on itself
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { VNode, MountedVNode } from './vdom'
|
import { VNode, MountedVNode } from './vdom'
|
||||||
import { Component, ComponentInstance, ComponentClass } from './component'
|
import { ComponentInstance, ComponentClass } from './component'
|
||||||
import { initializeState } from './componentState'
|
import { initializeState } from './componentState'
|
||||||
import { initializeProps } from './componentProps'
|
import { initializeProps } from './componentProps'
|
||||||
import { initializeWatch, teardownWatch } from './componentWatch'
|
import { initializeWatch, teardownWatch } from './componentWatch'
|
||||||
import { initializeComputed, teardownComputed } from './componentComputed'
|
import { initializeComputed, teardownComputed } from './componentComputed'
|
||||||
import { createRenderProxy } from './componentProxy'
|
import { ComponentProxy, createRenderProxy } from './componentProxy'
|
||||||
import { resolveComponentOptionsFromClass } from './componentOptions'
|
import { resolveComponentOptionsFromClass } from './componentOptions'
|
||||||
import { VNodeFlags } from './flags'
|
import { VNodeFlags } from './flags'
|
||||||
import { ErrorTypes, callLifecycleHookWithHandler } from './errorHandling'
|
import { ErrorTypes, callLifecycleHookWithHandler } from './errorHandling'
|
||||||
@ -14,25 +14,22 @@ import { EMPTY_OBJ } from '@vue/shared'
|
|||||||
let currentVNode: VNode | null = null
|
let currentVNode: VNode | null = null
|
||||||
let currentContextVNode: VNode | null = null
|
let currentContextVNode: VNode | null = null
|
||||||
|
|
||||||
export function createComponentInstance<T extends Component>(
|
export function createComponentInstance(vnode: VNode): ComponentInstance {
|
||||||
vnode: VNode
|
|
||||||
): ComponentInstance {
|
|
||||||
// component instance creation is done in two steps.
|
// component instance creation is done in two steps.
|
||||||
// first, `initializeComponentInstance` is called inside base component
|
// first, `initializeComponentInstance` is called inside base component
|
||||||
// constructor as the instance is created so that the extended component's
|
// constructor as the instance is created so that the extended component's
|
||||||
// constructor has access to certain properties and most importantly,
|
// constructor has access to public properties and most importantly props.
|
||||||
// this.$props.
|
|
||||||
// we are storing the vnodes in variables here so that there's no need to
|
// we are storing the vnodes in variables here so that there's no need to
|
||||||
// always pass args in super()
|
// always pass args in super()
|
||||||
currentVNode = vnode
|
currentVNode = vnode
|
||||||
currentContextVNode = vnode.contextVNode
|
currentContextVNode = vnode.contextVNode
|
||||||
const Component = vnode.tag as ComponentClass
|
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
|
// then we finish the initialization by collecting properties set on the
|
||||||
// instance
|
// instance
|
||||||
const {
|
const {
|
||||||
$proxy,
|
|
||||||
$options: { created, computed, watch }
|
$options: { created, computed, watch }
|
||||||
} = instance
|
} = instance
|
||||||
initializeState(instance, !Component.fromOptions)
|
initializeState(instance, !Component.fromOptions)
|
||||||
@ -41,7 +38,7 @@ export function createComponentInstance<T extends Component>(
|
|||||||
instance.$slots = currentVNode.slots || EMPTY_OBJ
|
instance.$slots = currentVNode.slots || EMPTY_OBJ
|
||||||
|
|
||||||
if (created) {
|
if (created) {
|
||||||
callLifecycleHookWithHandler(created, $proxy, ErrorTypes.CREATED)
|
callLifecycleHookWithHandler(created, instanceProxy, ErrorTypes.CREATED)
|
||||||
}
|
}
|
||||||
|
|
||||||
currentVNode = currentContextVNode = null
|
currentVNode = currentContextVNode = null
|
||||||
@ -50,8 +47,11 @@ export function createComponentInstance<T extends Component>(
|
|||||||
|
|
||||||
// this is called inside the base component's constructor
|
// this is called inside the base component's constructor
|
||||||
// it initializes all the way up to props so that they are available
|
// it initializes all the way up to props so that they are available
|
||||||
// inside the extended component's constructor
|
// inside the extended component's constructor, and returns the proxy of the
|
||||||
export function initializeComponentInstance(instance: ComponentInstance) {
|
// raw instance.
|
||||||
|
export function initializeComponentInstance<T extends ComponentInstance>(
|
||||||
|
instance: T
|
||||||
|
): ComponentProxy<T> {
|
||||||
if (__DEV__ && currentVNode === null) {
|
if (__DEV__ && currentVNode === null) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Component classes are not meant to be manually instantiated.`
|
`Component classes are not meant to be manually instantiated.`
|
||||||
@ -88,10 +88,12 @@ export function initializeComponentInstance(instance: ComponentInstance) {
|
|||||||
callLifecycleHookWithHandler(beforeCreate, proxy, ErrorTypes.BEFORE_CREATE)
|
callLifecycleHookWithHandler(beforeCreate, proxy, ErrorTypes.BEFORE_CREATE)
|
||||||
}
|
}
|
||||||
initializeProps(instance, props, (currentVNode as VNode).data)
|
initializeProps(instance, props, (currentVNode as VNode).data)
|
||||||
|
|
||||||
|
return proxy
|
||||||
}
|
}
|
||||||
|
|
||||||
export function teardownComponentInstance(instance: ComponentInstance) {
|
export function teardownComponentInstance(instance: ComponentInstance) {
|
||||||
const parentComponent = instance.$parent && instance.$parent.$self
|
const parentComponent = instance.$parent && instance.$parent._self
|
||||||
if (parentComponent && !parentComponent._unmounted) {
|
if (parentComponent && !parentComponent._unmounted) {
|
||||||
parentComponent.$children.splice(
|
parentComponent.$children.splice(
|
||||||
parentComponent.$children.indexOf(instance.$proxy),
|
parentComponent.$children.indexOf(instance.$proxy),
|
||||||
|
@ -44,13 +44,6 @@ export function initializeProps(
|
|||||||
? immutable(attrs)
|
? immutable(attrs)
|
||||||
: attrs
|
: attrs
|
||||||
: instance.$props
|
: 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.
|
// resolve raw VNode data.
|
||||||
|
@ -22,7 +22,7 @@ function getBoundMethod(fn: Function, target: any, receiver: any): Function {
|
|||||||
const renderProxyHandlers = {
|
const renderProxyHandlers = {
|
||||||
get(target: ComponentInstance<any, any>, key: string, receiver: any) {
|
get(target: ComponentInstance<any, any>, key: string, receiver: any) {
|
||||||
let i: any
|
let i: any
|
||||||
if (key === '$self') {
|
if (key === '_self') {
|
||||||
return target
|
return target
|
||||||
} else if ((i = target._rawData) !== null && i.hasOwnProperty(key)) {
|
} else if ((i = target._rawData) !== null && i.hasOwnProperty(key)) {
|
||||||
// data
|
// data
|
||||||
@ -86,6 +86,11 @@ const renderProxyHandlers = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createRenderProxy(instance: any): ComponentInstance {
|
export type ComponentProxy<T = ComponentInstance> = T & { _self: T }
|
||||||
return new Proxy(instance, renderProxyHandlers) as ComponentInstance
|
|
||||||
|
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
|
// a vnode may already have an instance if this is a compat call with
|
||||||
// new Vue()
|
// new Vue()
|
||||||
const instance = ((__COMPAT__ && vnode.children) ||
|
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
|
// inject platform-specific unmount to keep-alive container
|
||||||
if ((vnode.tag as any)[KeepAliveSymbol] === true) {
|
if ((vnode.tag as any)[KeepAliveSymbol] === true) {
|
||||||
|
@ -2,6 +2,7 @@ import { ComponentInstance } from './component'
|
|||||||
import { warn, pushWarningContext, popWarningContext } from './warning'
|
import { warn, pushWarningContext, popWarningContext } from './warning'
|
||||||
import { VNode } from './vdom'
|
import { VNode } from './vdom'
|
||||||
import { VNodeFlags } from './flags'
|
import { VNodeFlags } from './flags'
|
||||||
|
import { ComponentProxy } from './componentProxy'
|
||||||
|
|
||||||
export const enum ErrorTypes {
|
export const enum ErrorTypes {
|
||||||
BEFORE_CREATE = 1,
|
BEFORE_CREATE = 1,
|
||||||
@ -48,7 +49,7 @@ const ErrorTypeStrings: Record<number, string> = {
|
|||||||
|
|
||||||
export function callLifecycleHookWithHandler(
|
export function callLifecycleHookWithHandler(
|
||||||
hook: Function,
|
hook: Function,
|
||||||
instanceProxy: ComponentInstance,
|
instanceProxy: ComponentProxy,
|
||||||
type: ErrorTypes,
|
type: ErrorTypes,
|
||||||
arg?: any
|
arg?: any
|
||||||
) {
|
) {
|
||||||
@ -56,11 +57,11 @@ export function callLifecycleHookWithHandler(
|
|||||||
const res = hook.call(instanceProxy, arg)
|
const res = hook.call(instanceProxy, arg)
|
||||||
if (res && !res._isVue && typeof res.then === 'function') {
|
if (res && !res._isVue && typeof res.then === 'function') {
|
||||||
;(res as Promise<any>).catch(err => {
|
;(res as Promise<any>).catch(err => {
|
||||||
handleError(err, instanceProxy.$self, type)
|
handleError(err, instanceProxy._self, type)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
handleError(err, instanceProxy.$self, type)
|
handleError(err, instanceProxy._self, type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,10 +86,10 @@ export function handleError(
|
|||||||
cur = vnode.children as ComponentInstance
|
cur = vnode.children as ComponentInstance
|
||||||
}
|
}
|
||||||
} else if (instance) {
|
} else if (instance) {
|
||||||
cur = (instance as ComponentInstance).$parent
|
const parent = (instance as ComponentInstance).$parent
|
||||||
|
cur = parent && parent._self
|
||||||
}
|
}
|
||||||
while (cur) {
|
while (cur) {
|
||||||
cur = cur.$self
|
|
||||||
const handler = cur.errorCaptured
|
const handler = cur.errorCaptured
|
||||||
if (handler) {
|
if (handler) {
|
||||||
try {
|
try {
|
||||||
@ -103,7 +104,7 @@ export function handleError(
|
|||||||
logError(err2, ErrorTypes.ERROR_CAPTURED, contextVNode)
|
logError(err2, ErrorTypes.ERROR_CAPTURED, contextVNode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cur = cur.$parent
|
cur = cur.$parent && cur.$parent._self
|
||||||
}
|
}
|
||||||
logError(err, type, contextVNode)
|
logError(err, type, contextVNode)
|
||||||
}
|
}
|
||||||
@ -118,7 +119,7 @@ function logError(err: Error, type: ErrorTypes, contextVNode: VNode | null) {
|
|||||||
warn(
|
warn(
|
||||||
`Private fields cannot be accessed directly on \`this\` in a component ` +
|
`Private fields cannot be accessed directly on \`this\` in a component ` +
|
||||||
`class because they cannot be tunneled through Proxies. ` +
|
`class because they cannot be tunneled through Proxies. ` +
|
||||||
`Use \`this.$self.#field\` instead.`
|
`Use \`this._self.#field\` instead.`
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
warn(`Unhandled error${info ? ` ${info}` : ``}`)
|
warn(`Unhandled error${info ? ` ${info}` : ``}`)
|
||||||
|
@ -17,7 +17,7 @@ class Vue {
|
|||||||
// convert it to a class
|
// convert it to a class
|
||||||
const Component = createComponentClassFromOptions(options || {})
|
const Component = createComponentClassFromOptions(options || {})
|
||||||
const vnode = h(Component)
|
const vnode = h(Component)
|
||||||
const instance = createComponentInstance(vnode)
|
const instance = (vnode.children = createComponentInstance(vnode))
|
||||||
|
|
||||||
function mount(el: any) {
|
function mount(el: any) {
|
||||||
const dom = typeof el === 'string' ? document.querySelector(el) : el
|
const dom = typeof el === 'string' ? document.querySelector(el) : el
|
||||||
@ -26,10 +26,10 @@ class Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (options.el) {
|
if (options.el) {
|
||||||
return mount(options.el)
|
return mount(options.el) as any
|
||||||
} else {
|
} else {
|
||||||
;(instance as any).$mount = mount
|
;(instance as any).$mount = mount
|
||||||
return instance.$proxy
|
return instance.$proxy as any
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,8 @@
|
|||||||
"@vue/observer": ["packages/observer/src"],
|
"@vue/observer": ["packages/observer/src"],
|
||||||
"@vue/scheduler": ["packages/scheduler/src"],
|
"@vue/scheduler": ["packages/scheduler/src"],
|
||||||
"@vue/compiler-core": ["packages/compiler-core/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": [
|
"include": [
|
||||||
|
Loading…
Reference in New Issue
Block a user