Skip to content

Commit

Permalink
feat(runtime-core): improve component public instance proxy inspection
Browse files Browse the repository at this point in the history
  • Loading branch information
yyx990803 committed Apr 5, 2020
1 parent f42d11e commit 899287a
Show file tree
Hide file tree
Showing 5 changed files with 176 additions and 41 deletions.
14 changes: 12 additions & 2 deletions packages/runtime-core/__tests__/componentProxy.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,10 @@ describe('component: proxy', () => {
expect(instanceProxy.$attrs).toBe(instance!.attrs)
expect(instanceProxy.$slots).toBe(instance!.slots)
expect(instanceProxy.$refs).toBe(instance!.refs)
expect(instanceProxy.$parent).toBe(instance!.parent)
expect(instanceProxy.$root).toBe(instance!.root)
expect(instanceProxy.$parent).toBe(
instance!.parent && instance!.parent.proxy
)
expect(instanceProxy.$root).toBe(instance!.root.proxy)
expect(instanceProxy.$emit).toBe(instance!.emit)
expect(instanceProxy.$el).toBe(instance!.vnode.el)
expect(instanceProxy.$options).toBe(instance!.type)
Expand Down Expand Up @@ -174,6 +176,14 @@ describe('component: proxy', () => {
// set non-existent (goes into sink)
instanceProxy.baz = 1
expect('baz' in instanceProxy).toBe(true)

// dev mode ownKeys check for console inspection
expect(Object.keys(instanceProxy)).toMatchObject([
'msg',
'bar',
'foo',
'baz'
])
})

// #864
Expand Down
34 changes: 25 additions & 9 deletions packages/runtime-core/src/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,13 @@ import {
resetTracking
} from '@vue/reactivity'
import {
PublicInstanceProxyHandlers,
ComponentPublicInstance,
runtimeCompiledRenderProxyHandlers
ComponentPublicProxyTarget,
PublicInstanceProxyHandlers,
RuntimeCompiledPublicInstanceProxyHandlers,
createDevProxyTarget,
exposePropsOnDevProxyTarget,
exposeRenderContextOnDevProxyTarget
} from './componentProxy'
import { ComponentPropsOptions, resolveProps } from './componentProps'
import { Slots, resolveSlots } from './componentSlots'
Expand Down Expand Up @@ -139,6 +143,7 @@ export interface ComponentInternalInstance {
attrs: Data
slots: Slots
proxy: ComponentPublicInstance | null
proxyTarget: ComponentPublicProxyTarget
// alternative proxy used only for runtime-compiled render functions using
// `with` block
withProxy: ComponentPublicInstance | null
Expand Down Expand Up @@ -195,12 +200,13 @@ export function createComponentInstance(
parent,
appContext,
type: vnode.type as Component,
root: null!, // set later so it can point to itself
root: null!, // to be immediately set
next: null,
subTree: null!, // will be set synchronously right after creation
update: null!, // will be set synchronously right after creation
render: null,
proxy: null,
proxyTarget: null!, // to be immediately set
withProxy: null,
propsProxy: null,
setupContext: null,
Expand Down Expand Up @@ -250,6 +256,11 @@ export function createComponentInstance(
ec: null,
emit: null as any // to be set immediately
}
if (__DEV__) {
instance.proxyTarget = createDevProxyTarget(instance)
} else {
instance.proxyTarget = { _: instance }
}
instance.root = parent ? parent.root : instance
instance.emit = emit.bind(null, instance)
return instance
Expand Down Expand Up @@ -325,7 +336,10 @@ function setupStatefulComponent(
// 0. create render proxy property access cache
instance.accessCache = {}
// 1. create public instance / render proxy
instance.proxy = new Proxy(instance, PublicInstanceProxyHandlers)
instance.proxy = new Proxy(instance.proxyTarget, PublicInstanceProxyHandlers)
if (__DEV__) {
exposePropsOnDevProxyTarget(instance)
}
// 2. create props proxy
// the propsProxy is a reactive AND readonly proxy to the actual props.
// it will be updated in resolveProps() on updates before render
Expand Down Expand Up @@ -353,7 +367,7 @@ function setupStatefulComponent(
if (isSSR) {
// return the promise so server-renderer can wait on it
return setupResult.then((resolvedResult: unknown) => {
handleSetupResult(instance, resolvedResult, parentSuspense, isSSR)
handleSetupResult(instance, resolvedResult, isSSR)
})
} else if (__FEATURE_SUSPENSE__) {
// async setup returned Promise.
Expand All @@ -366,7 +380,7 @@ function setupStatefulComponent(
)
}
} else {
handleSetupResult(instance, setupResult, parentSuspense, isSSR)
handleSetupResult(instance, setupResult, isSSR)
}
} else {
finishComponentSetup(instance, isSSR)
Expand All @@ -376,7 +390,6 @@ function setupStatefulComponent(
export function handleSetupResult(
instance: ComponentInternalInstance,
setupResult: unknown,
parentSuspense: SuspenseBoundary | null,
isSSR: boolean
) {
if (isFunction(setupResult)) {
Expand All @@ -392,6 +405,9 @@ export function handleSetupResult(
// setup returned bindings.
// assuming a render function compiled from template is present.
instance.renderContext = reactive(setupResult)
if (__DEV__) {
exposeRenderContextOnDevProxyTarget(instance)
}
} else if (__DEV__ && setupResult !== undefined) {
warn(
`setup() should return an object. Received: ${
Expand Down Expand Up @@ -460,8 +476,8 @@ function finishComponentSetup(
// also only allows a whitelist of globals to fallthrough.
if (instance.render._rc) {
instance.withProxy = new Proxy(
instance,
runtimeCompiledRenderProxyHandlers
instance.proxyTarget,
RuntimeCompiledPublicInstanceProxyHandlers
)
}
}
Expand Down
46 changes: 36 additions & 10 deletions packages/runtime-core/src/componentOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,14 @@ import {
import {
reactive,
ComputedGetter,
WritableComputedOptions
WritableComputedOptions,
ComputedRef
} from '@vue/reactivity'
import { ComponentObjectPropsOptions, ExtractPropTypes } from './componentProps'
import {
ComponentObjectPropsOptions,
ExtractPropTypes,
normalizePropsOptions
} from './componentProps'
import { EmitsOptions } from './componentEmits'
import { Directive } from './directives'
import { ComponentPublicInstance } from './componentProxy'
Expand Down Expand Up @@ -239,6 +244,7 @@ export function applyOptions(
options: ComponentOptions,
asMixin: boolean = false
) {
const proxyTarget = instance.proxyTarget
const ctx = instance.proxy!
const {
// composition
Expand Down Expand Up @@ -277,7 +283,7 @@ export function applyOptions(

const globalMixins = instance.appContext.mixins
// call it only during dev
const checkDuplicateProperties = __DEV__ ? createDuplicateChecker() : null

// applyOptions is called non-as-mixin once per instance
if (!asMixin) {
callSyncHook('beforeCreate', options, ctx, globalMixins)
Expand All @@ -293,8 +299,10 @@ export function applyOptions(
applyMixins(instance, mixins)
}

const checkDuplicateProperties = __DEV__ ? createDuplicateChecker() : null

if (__DEV__ && propsOptions) {
for (const key in propsOptions) {
for (const key in normalizePropsOptions(propsOptions)[0]) {
checkDuplicateProperties!(OptionTypes.PROPS, key)
}
}
Expand All @@ -314,6 +322,7 @@ export function applyOptions(
if (__DEV__) {
for (const key in data) {
checkDuplicateProperties!(OptionTypes.DATA, key)
if (!(key in proxyTarget)) proxyTarget[key] = data[key]
}
}
instance.data = reactive(data)
Expand All @@ -326,9 +335,6 @@ export function applyOptions(
if (computedOptions) {
for (const key in computedOptions) {
const opt = (computedOptions as ComputedOptions)[key]

__DEV__ && checkDuplicateProperties!(OptionTypes.COMPUTED, key)

if (isFunction(opt)) {
renderContext[key] = computed(opt.bind(ctx, ctx))
} else {
Expand All @@ -350,15 +356,29 @@ export function applyOptions(
warn(`Computed property "${key}" has no getter.`)
}
}
if (__DEV__) {
checkDuplicateProperties!(OptionTypes.COMPUTED, key)
if (renderContext[key] && !(key in proxyTarget)) {
Object.defineProperty(proxyTarget, key, {
enumerable: true,
get: () => (renderContext[key] as ComputedRef).value
})
}
}
}
}

if (methods) {
for (const key in methods) {
const methodHandler = (methods as MethodOptions)[key]
if (isFunction(methodHandler)) {
__DEV__ && checkDuplicateProperties!(OptionTypes.METHODS, key)
renderContext[key] = methodHandler.bind(ctx)
if (__DEV__) {
checkDuplicateProperties!(OptionTypes.METHODS, key)
if (!(key in proxyTarget)) {
proxyTarget[key] = renderContext[key]
}
}
} else if (__DEV__) {
warn(
`Method "${key}" has type "${typeof methodHandler}" in the component definition. ` +
Expand Down Expand Up @@ -387,18 +407,24 @@ export function applyOptions(
if (isArray(injectOptions)) {
for (let i = 0; i < injectOptions.length; i++) {
const key = injectOptions[i]
__DEV__ && checkDuplicateProperties!(OptionTypes.INJECT, key)
renderContext[key] = inject(key)
if (__DEV__) {
checkDuplicateProperties!(OptionTypes.INJECT, key)
proxyTarget[key] = renderContext[key]
}
}
} else {
for (const key in injectOptions) {
__DEV__ && checkDuplicateProperties!(OptionTypes.INJECT, key)
const opt = injectOptions[key]
if (isObject(opt)) {
renderContext[key] = inject(opt.from, opt.default)
} else {
renderContext[key] = inject(opt)
}
if (__DEV__) {
checkDuplicateProperties!(OptionTypes.INJECT, key)
proxyTarget[key] = renderContext[key]
}
}
}
}
Expand Down
Loading

0 comments on commit 899287a

Please sign in to comment.