fix(options): data options should preserve original object if possible
This commit is contained in:
108
packages/runtime-core/src/componentProxy.ts
Normal file
108
packages/runtime-core/src/componentProxy.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
import { ComponentInternalInstance, Data } from './component'
|
||||
import { nextTick } from './scheduler'
|
||||
import { instanceWatch } from './apiWatch'
|
||||
import { EMPTY_OBJ, hasOwn } from '@vue/shared'
|
||||
import { ExtracComputedReturns } from './apiOptions'
|
||||
import { UnwrapRef } from '@vue/reactivity'
|
||||
|
||||
// public properties exposed on the proxy, which is used as the render context
|
||||
// in templates (as `this` in the render option)
|
||||
export type ComponentPublicInstance<
|
||||
P = {},
|
||||
B = {},
|
||||
D = {},
|
||||
C = {},
|
||||
M = {},
|
||||
PublicProps = P
|
||||
> = {
|
||||
$data: D
|
||||
$props: PublicProps
|
||||
$attrs: Data
|
||||
$refs: Data
|
||||
$slots: Data
|
||||
$root: ComponentInternalInstance | null
|
||||
$parent: ComponentInternalInstance | null
|
||||
$emit: (event: string, ...args: unknown[]) => void
|
||||
} & P &
|
||||
UnwrapRef<B> &
|
||||
D &
|
||||
ExtracComputedReturns<C> &
|
||||
M
|
||||
|
||||
export const PublicInstanceProxyHandlers = {
|
||||
get(target: ComponentInternalInstance, key: string) {
|
||||
const { renderContext, data, props, propsProxy } = target
|
||||
if (data !== EMPTY_OBJ && hasOwn(data, key)) {
|
||||
return data[key]
|
||||
} else if (hasOwn(renderContext, key)) {
|
||||
return renderContext[key]
|
||||
} else if (hasOwn(props, key)) {
|
||||
// return the value from propsProxy for ref unwrapping and readonly
|
||||
return (propsProxy as any)[key]
|
||||
} else {
|
||||
// TODO simplify this?
|
||||
switch (key) {
|
||||
case '$data':
|
||||
return data
|
||||
case '$props':
|
||||
return propsProxy
|
||||
case '$attrs':
|
||||
return target.attrs
|
||||
case '$slots':
|
||||
return target.slots
|
||||
case '$refs':
|
||||
return target.refs
|
||||
case '$parent':
|
||||
return target.parent
|
||||
case '$root':
|
||||
return target.root
|
||||
case '$emit':
|
||||
return target.emit
|
||||
case '$el':
|
||||
return target.vnode.el
|
||||
case '$options':
|
||||
return target.type
|
||||
default:
|
||||
// methods are only exposed when options are supported
|
||||
if (__FEATURE_OPTIONS__) {
|
||||
switch (key) {
|
||||
case '$forceUpdate':
|
||||
return target.update
|
||||
case '$nextTick':
|
||||
return nextTick
|
||||
case '$watch':
|
||||
return instanceWatch.bind(target)
|
||||
}
|
||||
}
|
||||
return target.user[key]
|
||||
}
|
||||
}
|
||||
},
|
||||
has(target: ComponentInternalInstance, key: string): boolean {
|
||||
const { renderContext, data, props } = target
|
||||
// TODO handle $xxx properties
|
||||
return (
|
||||
key[0] !== '_' &&
|
||||
((data !== EMPTY_OBJ && hasOwn(data, key)) ||
|
||||
hasOwn(renderContext, key) ||
|
||||
hasOwn(props, key))
|
||||
)
|
||||
},
|
||||
set(target: ComponentInternalInstance, key: string, value: any): boolean {
|
||||
const { data, renderContext } = target
|
||||
if (data !== EMPTY_OBJ && hasOwn(data, key)) {
|
||||
data[key] = value
|
||||
} else if (hasOwn(renderContext, key)) {
|
||||
renderContext[key] = value
|
||||
} else if (key[0] === '$' && key.slice(1) in target) {
|
||||
// TODO warn attempt of mutating public property
|
||||
return false
|
||||
} else if (key in target.props) {
|
||||
// TODO warn attempt of mutating prop
|
||||
return false
|
||||
} else {
|
||||
target.user[key] = value
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user