wip: separate setupState

This commit is contained in:
Evan You 2020-04-16 11:50:33 -04:00
parent 0709380c5f
commit b2662a62c5
4 changed files with 64 additions and 41 deletions

View File

@ -35,7 +35,7 @@ describe('component: proxy', () => {
expect(instance!.data.foo).toBe(2) expect(instance!.data.foo).toBe(2)
}) })
test('renderContext', () => { test('setupState', () => {
let instance: ComponentInternalInstance let instance: ComponentInternalInstance
let instanceProxy: any let instanceProxy: any
const Comp = { const Comp = {
@ -55,7 +55,7 @@ describe('component: proxy', () => {
render(h(Comp), nodeOps.createElement('div')) render(h(Comp), nodeOps.createElement('div'))
expect(instanceProxy.foo).toBe(1) expect(instanceProxy.foo).toBe(1)
instanceProxy.foo = 2 instanceProxy.foo = 2
expect(instance!.renderContext.foo).toBe(2) expect(instance!.setupState.foo).toBe(2)
}) })
test('should not expose non-declared props', () => { test('should not expose non-declared props', () => {

View File

@ -13,7 +13,7 @@ import {
RuntimeCompiledPublicInstanceProxyHandlers, RuntimeCompiledPublicInstanceProxyHandlers,
createDevProxyTarget, createDevProxyTarget,
exposePropsOnDevProxyTarget, exposePropsOnDevProxyTarget,
exposeRenderContextOnDevProxyTarget exposeSetupStateOnDevProxyTarget
} from './componentProxy' } from './componentProxy'
import { ComponentPropsOptions, initProps } from './componentProps' import { ComponentPropsOptions, initProps } from './componentProps'
import { Slots, initSlots, InternalSlots } from './componentSlots' import { Slots, initSlots, InternalSlots } from './componentSlots'
@ -143,6 +143,13 @@ export interface ComponentInternalInstance {
attrs: Data attrs: Data
slots: InternalSlots slots: InternalSlots
proxy: ComponentPublicInstance | null proxy: ComponentPublicInstance | null
refs: Data
emit: EmitFn
// setup
setupState: Data
setupContext: SetupContext | null
// The target object for the public instance proxy. In dev mode, we also // The target object for the public instance proxy. In dev mode, we also
// define getters for all known instance properties on it so it can be // define getters for all known instance properties on it so it can be
// properly inspected in the console. These getters are skipped in prod mode // properly inspected in the console. These getters are skipped in prod mode
@ -152,9 +159,6 @@ export interface ComponentInternalInstance {
// alternative proxy used only for runtime-compiled render functions using // alternative proxy used only for runtime-compiled render functions using
// `with` block // `with` block
withProxy: ComponentPublicInstance | null withProxy: ComponentPublicInstance | null
setupContext: SetupContext | null
refs: Data
emit: EmitFn
// suspense related // suspense related
suspense: SuspenseBoundary | null suspense: SuspenseBoundary | null
@ -209,19 +213,20 @@ export function createComponentInstance(
proxy: null, proxy: null,
proxyTarget: null!, // to be immediately set proxyTarget: null!, // to be immediately set
withProxy: null, withProxy: null,
setupContext: null,
effects: null, effects: null,
provides: parent ? parent.provides : Object.create(appContext.provides), provides: parent ? parent.provides : Object.create(appContext.provides),
accessCache: null!, accessCache: null!,
renderCache: [], renderCache: [],
// setup context properties // state
renderContext: EMPTY_OBJ, renderContext: EMPTY_OBJ,
data: EMPTY_OBJ, data: EMPTY_OBJ,
props: EMPTY_OBJ, props: EMPTY_OBJ,
attrs: EMPTY_OBJ, attrs: EMPTY_OBJ,
slots: EMPTY_OBJ, slots: EMPTY_OBJ,
refs: EMPTY_OBJ, refs: EMPTY_OBJ,
setupState: EMPTY_OBJ,
setupContext: null,
// per-instance asset storage (mutable during options resolution) // per-instance asset storage (mutable during options resolution)
components: Object.create(appContext.components), components: Object.create(appContext.components),
@ -392,9 +397,9 @@ export function handleSetupResult(
} }
// setup returned bindings. // setup returned bindings.
// assuming a render function compiled from template is present. // assuming a render function compiled from template is present.
instance.renderContext = reactive(setupResult) instance.setupState = reactive(setupResult)
if (__DEV__) { if (__DEV__) {
exposeRenderContextOnDevProxyTarget(instance) exposeSetupStateOnDevProxyTarget(instance)
} }
} else if (__DEV__ && setupResult !== undefined) { } else if (__DEV__ && setupResult !== undefined) {
warn( warn(

View File

@ -76,9 +76,10 @@ const publicPropertiesMap: Record<
} }
const enum AccessTypes { const enum AccessTypes {
SETUP,
DATA, DATA,
CONTEXT,
PROPS, PROPS,
CONTEXT,
OTHER OTHER
} }
@ -91,6 +92,7 @@ export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
get({ _: instance }: ComponentPublicProxyTarget, key: string) { get({ _: instance }: ComponentPublicProxyTarget, key: string) {
const { const {
renderContext, renderContext,
setupState,
data, data,
props, props,
accessCache, accessCache,
@ -109,6 +111,8 @@ export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
const n = accessCache![key] const n = accessCache![key]
if (n !== undefined) { if (n !== undefined) {
switch (n) { switch (n) {
case AccessTypes.SETUP:
return setupState[key]
case AccessTypes.DATA: case AccessTypes.DATA:
return data[key] return data[key]
case AccessTypes.CONTEXT: case AccessTypes.CONTEXT:
@ -117,24 +121,27 @@ 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)) {
accessCache![key] = AccessTypes.SETUP
return setupState[key]
} else if (data !== EMPTY_OBJ && hasOwn(data, key)) { } else if (data !== EMPTY_OBJ && hasOwn(data, key)) {
accessCache![key] = AccessTypes.DATA accessCache![key] = AccessTypes.DATA
return data[key] return data[key]
} else if (
// only cache other properties when instance has declared (thus stable)
// props
type.props &&
hasOwn(normalizePropsOptions(type.props)[0]!, key)
) {
accessCache![key] = AccessTypes.PROPS
return props![key]
} else if (renderContext !== EMPTY_OBJ && hasOwn(renderContext, key)) { } else if (renderContext !== EMPTY_OBJ && hasOwn(renderContext, key)) {
accessCache![key] = AccessTypes.CONTEXT accessCache![key] = AccessTypes.CONTEXT
return renderContext[key] return renderContext[key]
} else if (type.props) {
// only cache other properties when instance has declared (thus stable)
// props
if (hasOwn(normalizePropsOptions(type.props)[0]!, key)) {
accessCache![key] = AccessTypes.PROPS
// return the value from propsProxy for ref unwrapping and readonly
return props![key]
} else { } else {
accessCache![key] = AccessTypes.OTHER accessCache![key] = AccessTypes.OTHER
} }
} }
}
// public $xxx properties & // public $xxx properties &
// user-attached properties (falls through to proxyTarget) // user-attached properties (falls through to proxyTarget)
@ -170,9 +177,18 @@ export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
key: string, key: string,
value: any value: any
): boolean { ): boolean {
const { data, renderContext } = instance const { data, setupState, renderContext } = instance
if (data !== EMPTY_OBJ && hasOwn(data, key)) { if (setupState !== EMPTY_OBJ && hasOwn(setupState, key)) {
setupState[key] = value
} else if (data !== EMPTY_OBJ && hasOwn(data, key)) {
data[key] = value data[key] = value
} else if (key in instance.props) {
__DEV__ &&
warn(
`Attempting to mutate prop "${key}". Props are readonly.`,
instance
)
return false
} else if (hasOwn(renderContext, key)) { } else if (hasOwn(renderContext, key)) {
renderContext[key] = value renderContext[key] = value
} else if (key[0] === '$' && key.slice(1) in instance) { } else if (key[0] === '$' && key.slice(1) in instance) {
@ -183,13 +199,6 @@ export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
instance instance
) )
return false return false
} else if (key in instance.props) {
__DEV__ &&
warn(
`Attempting to mutate prop "${key}". Props are readonly.`,
instance
)
return false
} else { } else {
if (__DEV__ && key in instance.appContext.config.globalProperties) { if (__DEV__ && key in instance.appContext.config.globalProperties) {
Object.defineProperty(instance.proxyTarget, key, { Object.defineProperty(instance.proxyTarget, key, {
@ -206,15 +215,24 @@ export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
has( has(
{ {
_: { data, accessCache, renderContext, type, proxyTarget, appContext } _: {
data,
setupState,
accessCache,
renderContext,
type,
proxyTarget,
appContext
}
}: ComponentPublicProxyTarget, }: ComponentPublicProxyTarget,
key: string key: string
) { ) {
return ( return (
accessCache![key] !== undefined || accessCache![key] !== undefined ||
(data !== EMPTY_OBJ && hasOwn(data, key)) || (data !== EMPTY_OBJ && hasOwn(data, key)) ||
hasOwn(renderContext, key) || (setupState !== EMPTY_OBJ && hasOwn(setupState, key)) ||
(type.props && hasOwn(normalizePropsOptions(type.props)[0]!, key)) || (type.props && hasOwn(normalizePropsOptions(type.props)[0]!, key)) ||
hasOwn(renderContext, key) ||
hasOwn(publicPropertiesMap, key) || hasOwn(publicPropertiesMap, key) ||
hasOwn(proxyTarget, key) || hasOwn(proxyTarget, key) ||
hasOwn(appContext.config.globalProperties, key) hasOwn(appContext.config.globalProperties, key)
@ -306,15 +324,15 @@ export function exposePropsOnDevProxyTarget(
} }
} }
export function exposeRenderContextOnDevProxyTarget( export function exposeSetupStateOnDevProxyTarget(
instance: ComponentInternalInstance instance: ComponentInternalInstance
) { ) {
const { proxyTarget, renderContext } = instance const { proxyTarget, setupState } = instance
Object.keys(toRaw(renderContext)).forEach(key => { Object.keys(toRaw(setupState)).forEach(key => {
Object.defineProperty(proxyTarget, key, { Object.defineProperty(proxyTarget, key, {
enumerable: true, enumerable: true,
configurable: true, configurable: true,
get: () => renderContext[key], get: () => setupState[key],
set: NOOP set: NOOP
}) })
}) })

View File

@ -1899,14 +1899,14 @@ function baseCreateRenderer(
} }
const oldRef = oldRawRef && oldRawRef[1] const oldRef = oldRawRef && oldRawRef[1]
const refs = owner.refs === EMPTY_OBJ ? (owner.refs = {}) : owner.refs const refs = owner.refs === EMPTY_OBJ ? (owner.refs = {}) : owner.refs
const renderContext = owner.renderContext const setupState = owner.setupState
// unset old ref // unset old ref
if (oldRef != null && oldRef !== ref) { if (oldRef != null && oldRef !== ref) {
if (isString(oldRef)) { if (isString(oldRef)) {
refs[oldRef] = null refs[oldRef] = null
if (hasOwn(renderContext, oldRef)) { if (hasOwn(setupState, oldRef)) {
renderContext[oldRef] = null setupState[oldRef] = null
} }
} else if (isRef(oldRef)) { } else if (isRef(oldRef)) {
oldRef.value = null oldRef.value = null
@ -1915,8 +1915,8 @@ function baseCreateRenderer(
if (isString(ref)) { if (isString(ref)) {
refs[ref] = value refs[ref] = value
if (hasOwn(renderContext, ref)) { if (hasOwn(setupState, ref)) {
renderContext[ref] = value setupState[ref] = value
} }
} else if (isRef(ref)) { } else if (isRef(ref)) {
ref.value = value ref.value = value