fix(compat): fix $options mutation + adjust private API initialization

close #10626
close #10636
This commit is contained in:
Evan You 2024-04-15 19:28:37 +08:00
parent 04af9504a7
commit d58d133b1c
No known key found for this signature in database
GPG Key ID: B9D421896CA450FB
3 changed files with 116 additions and 40 deletions

View File

@ -15,6 +15,7 @@ import {
DeprecationTypes,
assertCompatEnabled,
isCompatEnabled,
warnDeprecation,
} from './compatConfig'
import { off, on, once } from './instanceEventEmitter'
import { getCompatListeners } from './instanceListeners'
@ -121,50 +122,77 @@ export function installCompatInstanceProperties(map: PublicPropertiesMap) {
$children: getCompatChildren,
$listeners: getCompatListeners,
// inject additional properties into $options for compat
// e.g. vuex needs this.$options.parent
$options: i => {
if (!isCompatEnabled(DeprecationTypes.PRIVATE_APIS, i)) {
return resolveMergedOptions(i)
}
if (i.resolvedOptions) {
return i.resolvedOptions
}
const res = (i.resolvedOptions = extend({}, resolveMergedOptions(i)))
Object.defineProperties(res, {
parent: {
get() {
warnDeprecation(DeprecationTypes.PRIVATE_APIS, i, '$options.parent')
return i.proxy!.$parent
},
},
propsData: {
get() {
warnDeprecation(
DeprecationTypes.PRIVATE_APIS,
i,
'$options.propsData',
)
return i.vnode.props
},
},
})
return res
},
} as PublicPropertiesMap)
/* istanbul ignore if */
if (isCompatEnabled(DeprecationTypes.PRIVATE_APIS, null)) {
extend(map, {
// needed by many libs / render fns
$vnode: i => i.vnode,
const privateAPIs = {
// needed by many libs / render fns
$vnode: i => i.vnode,
// inject additional properties into $options for compat
// e.g. vuex needs this.$options.parent
$options: i => {
const res = extend({}, resolveMergedOptions(i))
res.parent = i.proxy!.$parent
res.propsData = i.vnode.props
return res
},
// some private properties that are likely accessed...
_self: i => i.proxy,
_uid: i => i.uid,
_data: i => i.data,
_isMounted: i => i.isMounted,
_isDestroyed: i => i.isUnmounted,
// some private properties that are likely accessed...
_self: i => i.proxy,
_uid: i => i.uid,
_data: i => i.data,
_isMounted: i => i.isMounted,
_isDestroyed: i => i.isUnmounted,
// v2 render helpers
$createElement: () => compatH,
_c: () => compatH,
_o: () => legacyMarkOnce,
_n: () => looseToNumber,
_s: () => toDisplayString,
_l: () => renderList,
_t: i => legacyRenderSlot.bind(null, i),
_q: () => looseEqual,
_i: () => looseIndexOf,
_m: i => legacyRenderStatic.bind(null, i),
_f: () => resolveFilter,
_k: i => legacyCheckKeyCodes.bind(null, i),
_b: () => legacyBindObjectProps,
_v: () => createTextVNode,
_e: () => createCommentVNode,
_u: () => legacyresolveScopedSlots,
_g: () => legacyBindObjectListeners,
_d: () => legacyBindDynamicKeys,
_p: () => legacyPrependModifier,
} as PublicPropertiesMap
// v2 render helpers
$createElement: () => compatH,
_c: () => compatH,
_o: () => legacyMarkOnce,
_n: () => looseToNumber,
_s: () => toDisplayString,
_l: () => renderList,
_t: i => legacyRenderSlot.bind(null, i),
_q: () => looseEqual,
_i: () => looseIndexOf,
_m: i => legacyRenderStatic.bind(null, i),
_f: () => resolveFilter,
_k: i => legacyCheckKeyCodes.bind(null, i),
_b: () => legacyBindObjectProps,
_v: () => createTextVNode,
_e: () => createCommentVNode,
_u: () => legacyresolveScopedSlots,
_g: () => legacyBindObjectListeners,
_d: () => legacyBindDynamicKeys,
_p: () => legacyPrependModifier,
} as PublicPropertiesMap)
for (const key in privateAPIs) {
map[key] = i => {
if (isCompatEnabled(DeprecationTypes.PRIVATE_APIS, i)) {
return privateAPIs[key](i)
}
}
}
}

View File

@ -45,6 +45,7 @@ import { type Directive, validateDirectiveName } from './directives'
import {
type ComponentOptions,
type ComputedOptions,
type MergedComponentOptions,
type MethodOptions,
applyOptions,
resolveMergedOptions,
@ -524,6 +525,12 @@ export interface ComponentInternalInstance {
* @internal
*/
getCssVars?: () => Record<string, string>
/**
* v2 compat only, for caching mutated $options
* @internal
*/
resolvedOptions?: MergedComponentOptions
}
const emptyAppContext = createAppContext()

View File

@ -14,6 +14,7 @@ beforeEach(() => {
Vue.configureCompat({
MODE: 2,
GLOBAL_MOUNT: 'suppress-warning',
PRIVATE_APIS: 'suppress-warning',
})
})
@ -331,3 +332,43 @@ test('INSTANCE_ATTR_CLASS_STYLE', () => {
)('Anonymous'),
).toHaveBeenWarned()
})
test('$options mutation', () => {
const Comp = {
props: ['id'],
template: '<div/>',
data() {
return {
foo: '',
}
},
created(this: any) {
expect(this.$options.parent).toBeDefined()
expect(this.$options.test).toBeUndefined()
this.$options.test = this.id
expect(this.$options.test).toBe(this.id)
},
}
new Vue({
template: `<div><Comp id="1"/><Comp id="2"/></div>`,
components: { Comp },
}).$mount()
})
test('other private APIs', () => {
new Vue({
created() {
expect(this.$createElement).toBeTruthy()
},
})
new Vue({
compatConfig: {
PRIVATE_APIS: false,
},
created() {
expect(this.$createElement).toBeUndefined()
},
})
})