mirror of https://github.com/vuejs/core.git
fix(sfc): ensure `<script setup>` binding behavior consistency on `this` between prod and dev
close #6248
This commit is contained in:
parent
15e889afaf
commit
f73925d76a
|
@ -4,7 +4,8 @@ import {
|
||||||
getCurrentInstance,
|
getCurrentInstance,
|
||||||
nodeOps,
|
nodeOps,
|
||||||
createApp,
|
createApp,
|
||||||
shallowReadonly
|
shallowReadonly,
|
||||||
|
defineComponent
|
||||||
} from '@vue/runtime-test'
|
} from '@vue/runtime-test'
|
||||||
import { ComponentInternalInstance, ComponentOptions } from '../src/component'
|
import { ComponentInternalInstance, ComponentOptions } from '../src/component'
|
||||||
|
|
||||||
|
@ -458,4 +459,24 @@ describe('component: proxy', () => {
|
||||||
)} was accessed during render ` + `but is not defined on instance.`
|
)} was accessed during render ` + `but is not defined on instance.`
|
||||||
).toHaveBeenWarned()
|
).toHaveBeenWarned()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('should prevent mutating script setup bindings', () => {
|
||||||
|
const Comp = defineComponent({
|
||||||
|
render() {},
|
||||||
|
setup() {
|
||||||
|
return {
|
||||||
|
__isScriptSetup: true,
|
||||||
|
foo: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
expect('foo' in this).toBe(false)
|
||||||
|
try {
|
||||||
|
this.foo = 123
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
render(h(Comp), nodeOps.createElement('div'))
|
||||||
|
expect(`Cannot mutate <script setup> binding "foo"`).toHaveBeenWarned()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -270,6 +270,9 @@ export interface ComponentRenderContext {
|
||||||
|
|
||||||
export const isReservedPrefix = (key: string) => key === '_' || key === '$'
|
export const isReservedPrefix = (key: string) => key === '_' || key === '$'
|
||||||
|
|
||||||
|
const hasSetupBinding = (state: Data, key: string) =>
|
||||||
|
state !== EMPTY_OBJ && !state.__isScriptSetup && hasOwn(state, key)
|
||||||
|
|
||||||
export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
|
export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
|
||||||
get({ _: instance }: ComponentRenderContext, key: string) {
|
get({ _: instance }: ComponentRenderContext, key: string) {
|
||||||
const { ctx, setupState, data, props, accessCache, type, appContext } =
|
const { ctx, setupState, data, props, accessCache, type, appContext } =
|
||||||
|
@ -280,19 +283,6 @@ export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// prioritize <script setup> bindings during dev.
|
|
||||||
// this allows even properties that start with _ or $ to be used - so that
|
|
||||||
// it aligns with the production behavior where the render fn is inlined and
|
|
||||||
// indeed has access to all declared variables.
|
|
||||||
if (
|
|
||||||
__DEV__ &&
|
|
||||||
setupState !== EMPTY_OBJ &&
|
|
||||||
setupState.__isScriptSetup &&
|
|
||||||
hasOwn(setupState, key)
|
|
||||||
) {
|
|
||||||
return setupState[key]
|
|
||||||
}
|
|
||||||
|
|
||||||
// data / props / ctx
|
// data / props / ctx
|
||||||
// This getter gets called for every property access on the render context
|
// This getter gets called for every property access on the render context
|
||||||
// during render and is a major hotspot. The most expensive part of this
|
// during render and is a major hotspot. The most expensive part of this
|
||||||
|
@ -314,7 +304,7 @@ export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
|
||||||
return props![key]
|
return props![key]
|
||||||
// default: just fallthrough
|
// default: just fallthrough
|
||||||
}
|
}
|
||||||
} else if (setupState !== EMPTY_OBJ && hasOwn(setupState, key)) {
|
} else if (hasSetupBinding(setupState, key)) {
|
||||||
accessCache![key] = AccessTypes.SETUP
|
accessCache![key] = AccessTypes.SETUP
|
||||||
return setupState[key]
|
return setupState[key]
|
||||||
} else if (data !== EMPTY_OBJ && hasOwn(data, key)) {
|
} else if (data !== EMPTY_OBJ && hasOwn(data, key)) {
|
||||||
|
@ -403,26 +393,28 @@ export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
|
||||||
value: any
|
value: any
|
||||||
): boolean {
|
): boolean {
|
||||||
const { data, setupState, ctx } = instance
|
const { data, setupState, ctx } = instance
|
||||||
if (setupState !== EMPTY_OBJ && hasOwn(setupState, key)) {
|
if (hasSetupBinding(setupState, key)) {
|
||||||
setupState[key] = value
|
setupState[key] = value
|
||||||
return true
|
return true
|
||||||
|
} else if (
|
||||||
|
__DEV__ &&
|
||||||
|
setupState.__isScriptSetup &&
|
||||||
|
hasOwn(setupState, key)
|
||||||
|
) {
|
||||||
|
warn(`Cannot mutate <script setup> binding "${key}" from Options API.`)
|
||||||
|
return false
|
||||||
} else if (data !== EMPTY_OBJ && hasOwn(data, key)) {
|
} else if (data !== EMPTY_OBJ && hasOwn(data, key)) {
|
||||||
data[key] = value
|
data[key] = value
|
||||||
return true
|
return true
|
||||||
} else if (hasOwn(instance.props, key)) {
|
} else if (hasOwn(instance.props, key)) {
|
||||||
__DEV__ &&
|
__DEV__ && warn(`Attempting to mutate prop "${key}". Props are readonly.`)
|
||||||
warn(
|
|
||||||
`Attempting to mutate prop "${key}". Props are readonly.`,
|
|
||||||
instance
|
|
||||||
)
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if (key[0] === '$' && key.slice(1) in instance) {
|
if (key[0] === '$' && key.slice(1) in instance) {
|
||||||
__DEV__ &&
|
__DEV__ &&
|
||||||
warn(
|
warn(
|
||||||
`Attempting to mutate public property "${key}". ` +
|
`Attempting to mutate public property "${key}". ` +
|
||||||
`Properties starting with $ are reserved and readonly.`,
|
`Properties starting with $ are reserved and readonly.`
|
||||||
instance
|
|
||||||
)
|
)
|
||||||
return false
|
return false
|
||||||
} else {
|
} else {
|
||||||
|
@ -449,7 +441,7 @@ export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
|
||||||
return (
|
return (
|
||||||
!!accessCache![key] ||
|
!!accessCache![key] ||
|
||||||
(data !== EMPTY_OBJ && hasOwn(data, key)) ||
|
(data !== EMPTY_OBJ && hasOwn(data, key)) ||
|
||||||
(setupState !== EMPTY_OBJ && hasOwn(setupState, key)) ||
|
hasSetupBinding(setupState, key) ||
|
||||||
((normalizedProps = propsOptions[0]) && hasOwn(normalizedProps, key)) ||
|
((normalizedProps = propsOptions[0]) && hasOwn(normalizedProps, key)) ||
|
||||||
hasOwn(ctx, key) ||
|
hasOwn(ctx, key) ||
|
||||||
hasOwn(publicPropertiesMap, key) ||
|
hasOwn(publicPropertiesMap, key) ||
|
||||||
|
|
Loading…
Reference in New Issue