Skip to content
Permalink
Browse files
fix(sfc): ensure <script setup> binding behavior consistency on `th…
…is` between prod and dev

close #6248
  • Loading branch information
yyx990803 committed Nov 10, 2022
1 parent 15e889a commit f73925d76a76ee259749b8b48cb68895f539a00f
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 24 deletions.
@@ -4,7 +4,8 @@ import {
getCurrentInstance,
nodeOps,
createApp,
shallowReadonly
shallowReadonly,
defineComponent
} from '@vue/runtime-test'
import { ComponentInternalInstance, ComponentOptions } from '../src/component'

@@ -458,4 +459,24 @@ describe('component: proxy', () => {
)} was accessed during render ` + `but is not defined on instance.`
).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 === '$'

const hasSetupBinding = (state: Data, key: string) =>
state !== EMPTY_OBJ && !state.__isScriptSetup && hasOwn(state, key)

export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
get({ _: instance }: ComponentRenderContext, key: string) {
const { ctx, setupState, data, props, accessCache, type, appContext } =
@@ -280,19 +283,6 @@ export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
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
// 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
@@ -314,7 +304,7 @@ export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
return props![key]
// default: just fallthrough
}
} else if (setupState !== EMPTY_OBJ && hasOwn(setupState, key)) {
} else if (hasSetupBinding(setupState, key)) {
accessCache![key] = AccessTypes.SETUP
return setupState[key]
} else if (data !== EMPTY_OBJ && hasOwn(data, key)) {
@@ -403,26 +393,28 @@ export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
value: any
): boolean {
const { data, setupState, ctx } = instance
if (setupState !== EMPTY_OBJ && hasOwn(setupState, key)) {
if (hasSetupBinding(setupState, key)) {
setupState[key] = value
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)) {
data[key] = value
return true
} else if (hasOwn(instance.props, key)) {
__DEV__ &&
warn(
`Attempting to mutate prop "${key}". Props are readonly.`,
instance
)
__DEV__ && warn(`Attempting to mutate prop "${key}". Props are readonly.`)
return false
}
if (key[0] === '$' && key.slice(1) in instance) {
__DEV__ &&
warn(
`Attempting to mutate public property "${key}". ` +
`Properties starting with $ are reserved and readonly.`,
instance
`Properties starting with $ are reserved and readonly.`
)
return false
} else {
@@ -449,7 +441,7 @@ export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
return (
!!accessCache![key] ||
(data !== EMPTY_OBJ && hasOwn(data, key)) ||
(setupState !== EMPTY_OBJ && hasOwn(setupState, key)) ||
hasSetupBinding(setupState, key) ||
((normalizedProps = propsOptions[0]) && hasOwn(normalizedProps, key)) ||
hasOwn(ctx, key) ||
hasOwn(publicPropertiesMap, key) ||

0 comments on commit f73925d

Please sign in to comment.