test(vapor): api expose (partial)
This commit is contained in:
parent
baf68a0fe4
commit
12ef12105b
|
@ -380,16 +380,13 @@ export interface GenericComponentInstance {
|
|||
|
||||
// exposed properties via expose()
|
||||
exposed: Record<string, any> | null
|
||||
exposeProxy: Record<string, any> | null
|
||||
|
||||
/**
|
||||
* setup related
|
||||
* @internal
|
||||
*/
|
||||
setupState?: Data
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
setupContext?: any
|
||||
/**
|
||||
* devtools access to additional info
|
||||
* @internal
|
||||
|
@ -603,6 +600,10 @@ export interface ComponentInternalInstance extends GenericComponentInstance {
|
|||
* @internal
|
||||
*/
|
||||
setupState: Data
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
setupContext?: SetupContext | null
|
||||
|
||||
// main proxy that serves as the public instance (`this`)
|
||||
proxy: ComponentPublicInstance | null
|
||||
|
@ -1131,30 +1132,6 @@ function getSlotsProxy(instance: ComponentInternalInstance): Slots {
|
|||
export function createSetupContext(
|
||||
instance: ComponentInternalInstance,
|
||||
): SetupContext {
|
||||
const expose: SetupContext['expose'] = exposed => {
|
||||
if (__DEV__) {
|
||||
if (instance.exposed) {
|
||||
warn(`expose() should be called only once per setup().`)
|
||||
}
|
||||
if (exposed != null) {
|
||||
let exposedType: string = typeof exposed
|
||||
if (exposedType === 'object') {
|
||||
if (isArray(exposed)) {
|
||||
exposedType = 'array'
|
||||
} else if (isRef(exposed)) {
|
||||
exposedType = 'ref'
|
||||
}
|
||||
}
|
||||
if (exposedType !== 'object') {
|
||||
warn(
|
||||
`expose() should be passed a plain object, received ${exposedType}.`,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
instance.exposed = exposed || {}
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
// We use getters in dev in case libs like test-utils overwrite instance
|
||||
// properties (overwrites should not be done in prod)
|
||||
|
@ -1173,46 +1150,69 @@ export function createSetupContext(
|
|||
get emit() {
|
||||
return (event: string, ...args: any[]) => instance.emit(event, ...args)
|
||||
},
|
||||
expose,
|
||||
expose: exposed => expose(instance, exposed as any),
|
||||
})
|
||||
} else {
|
||||
return {
|
||||
attrs: new Proxy(instance.attrs, attrsProxyHandlers),
|
||||
slots: instance.slots,
|
||||
emit: instance.emit,
|
||||
expose,
|
||||
expose: exposed => expose(instance, exposed as any),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getComponentPublicInstance(
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export function expose(
|
||||
instance: GenericComponentInstance,
|
||||
exposed: Record<string, any>,
|
||||
): void {
|
||||
if (__DEV__) {
|
||||
if (instance.exposed) {
|
||||
warn(`expose() should be called only once per setup().`)
|
||||
}
|
||||
if (exposed != null) {
|
||||
let exposedType: string = typeof exposed
|
||||
if (exposedType === 'object') {
|
||||
if (isArray(exposed)) {
|
||||
exposedType = 'array'
|
||||
} else if (isRef(exposed)) {
|
||||
exposedType = 'ref'
|
||||
}
|
||||
}
|
||||
if (exposedType !== 'object') {
|
||||
warn(
|
||||
`expose() should be passed a plain object, received ${exposedType}.`,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
instance.exposed = exposed || {}
|
||||
}
|
||||
|
||||
export function getComponentPublicInstance(
|
||||
instance: ComponentInternalInstance,
|
||||
): ComponentPublicInstance | ComponentInternalInstance['exposed'] | null {
|
||||
if (instance.exposed) {
|
||||
if ('exposeProxy' in instance) {
|
||||
return (
|
||||
instance.exposeProxy ||
|
||||
(instance.exposeProxy = new Proxy(
|
||||
proxyRefs(markRaw(instance.exposed)),
|
||||
{
|
||||
get(target, key: string) {
|
||||
if (key in target) {
|
||||
return target[key]
|
||||
} else if (key in publicPropertiesMap) {
|
||||
return publicPropertiesMap[key](
|
||||
instance as ComponentInternalInstance,
|
||||
)
|
||||
}
|
||||
},
|
||||
has(target, key: string) {
|
||||
return key in target || key in publicPropertiesMap
|
||||
},
|
||||
},
|
||||
))
|
||||
)
|
||||
} else {
|
||||
return instance.exposed
|
||||
}
|
||||
return (
|
||||
instance.exposeProxy ||
|
||||
(instance.exposeProxy = new Proxy(proxyRefs(markRaw(instance.exposed)), {
|
||||
get(target, key: string) {
|
||||
if (key in target) {
|
||||
return target[key]
|
||||
} else if (key in publicPropertiesMap) {
|
||||
return publicPropertiesMap[key](
|
||||
instance as ComponentInternalInstance,
|
||||
)
|
||||
}
|
||||
},
|
||||
has(target, key: string) {
|
||||
return key in target || key in publicPropertiesMap
|
||||
},
|
||||
}))
|
||||
)
|
||||
} else {
|
||||
return instance.proxy
|
||||
}
|
||||
|
|
|
@ -499,6 +499,7 @@ export {
|
|||
type ComponentInternalOptions,
|
||||
type GenericComponentInstance,
|
||||
type LifecycleHook,
|
||||
expose,
|
||||
nextUid,
|
||||
validateComponentName,
|
||||
} from './component'
|
||||
|
|
|
@ -6,7 +6,7 @@ import type { RawProps } from '../src/componentProps'
|
|||
export interface RenderContext {
|
||||
component: VaporComponent
|
||||
host: HTMLElement
|
||||
instance: VaporComponentInstance | undefined
|
||||
instance: Record<string, any> | undefined
|
||||
app: App
|
||||
create: (props?: RawProps) => RenderContext
|
||||
mount: (container?: string | ParentNode) => RenderContext
|
||||
|
|
|
@ -6,25 +6,22 @@ import { currentInstance } from '@vue/runtime-dom'
|
|||
import { defineVaporComponent } from '../src/apiDefineComponent'
|
||||
|
||||
const define = makeRender()
|
||||
describe.todo('api: expose', () => {
|
||||
test('via setup context', () => {
|
||||
|
||||
describe('api: expose', () => {
|
||||
test.todo('via setup context + template ref', () => {
|
||||
const Child = defineVaporComponent({
|
||||
setup(_, { expose }) {
|
||||
expose({
|
||||
foo: 1,
|
||||
bar: ref(2),
|
||||
})
|
||||
return {
|
||||
bar: ref(3),
|
||||
baz: ref(4),
|
||||
}
|
||||
return []
|
||||
},
|
||||
})
|
||||
const childRef = ref()
|
||||
define({
|
||||
render: () => {
|
||||
const n0 = createComponent(Child)
|
||||
setRef(n0, childRef)
|
||||
return n0
|
||||
},
|
||||
}).render()
|
||||
|
@ -35,7 +32,7 @@ describe.todo('api: expose', () => {
|
|||
expect(childRef.value.baz).toBeUndefined()
|
||||
})
|
||||
|
||||
test('via setup context (expose empty)', () => {
|
||||
test.todo('via setup context + template ref (expose empty)', () => {
|
||||
let childInstance: VaporComponentInstance | null = null
|
||||
const Child = defineVaporComponent({
|
||||
setup(_) {
|
||||
|
@ -62,13 +59,11 @@ describe.todo('api: expose', () => {
|
|||
expose({
|
||||
foo: 1,
|
||||
})
|
||||
return {
|
||||
bar: 2,
|
||||
}
|
||||
return []
|
||||
},
|
||||
}).render()
|
||||
expect(instance!.exposed!.foo).toBe(1)
|
||||
expect(instance!.exposed!.bar).toBe(undefined)
|
||||
expect(instance!.foo).toBe(1)
|
||||
expect(instance!.bar).toBe(undefined)
|
||||
})
|
||||
|
||||
test('warning for ref', () => {
|
||||
|
|
|
@ -1,13 +1,7 @@
|
|||
import type { SetupContext } from '../src/component'
|
||||
import {
|
||||
createComponent,
|
||||
defineComponent,
|
||||
ref,
|
||||
template,
|
||||
useAttrs,
|
||||
useSlots,
|
||||
} from '../src'
|
||||
import { createComponent, defineVaporComponent, template } from '../src'
|
||||
import { ref, useAttrs, useSlots } from '@vue/runtime-dom'
|
||||
import { makeRender } from './_utils'
|
||||
import type { VaporComponentInstance } from '../src/component'
|
||||
|
||||
const define = makeRender<any>()
|
||||
|
||||
|
@ -15,15 +9,17 @@ describe.todo('SFC <script setup> helpers', () => {
|
|||
test.todo('should warn runtime usage', () => {})
|
||||
|
||||
test('useSlots / useAttrs (no args)', () => {
|
||||
let slots: SetupContext['slots'] | undefined
|
||||
let attrs: SetupContext['attrs'] | undefined
|
||||
let slots: VaporComponentInstance['slots'] | undefined
|
||||
let attrs: VaporComponentInstance['attrs'] | undefined
|
||||
|
||||
const Comp = {
|
||||
const Comp = defineVaporComponent({
|
||||
setup() {
|
||||
// @ts-expect-error
|
||||
slots = useSlots()
|
||||
attrs = useAttrs()
|
||||
return []
|
||||
},
|
||||
}
|
||||
})
|
||||
const count = ref(0)
|
||||
const passedAttrs = { id: () => count.value }
|
||||
const passedSlots = {
|
||||
|
@ -45,14 +41,16 @@ describe.todo('SFC <script setup> helpers', () => {
|
|||
})
|
||||
|
||||
test('useSlots / useAttrs (with args)', () => {
|
||||
let slots: SetupContext['slots'] | undefined
|
||||
let attrs: SetupContext['attrs'] | undefined
|
||||
let ctx: SetupContext | undefined
|
||||
const Comp = defineComponent({
|
||||
let slots: VaporComponentInstance['slots'] | undefined
|
||||
let attrs: VaporComponentInstance['attrs'] | undefined
|
||||
let ctx: VaporComponentInstance | undefined
|
||||
const Comp = defineVaporComponent({
|
||||
setup(_, _ctx) {
|
||||
// @ts-expect-error
|
||||
slots = useSlots()
|
||||
attrs = useAttrs()
|
||||
ctx = _ctx
|
||||
ctx = _ctx as VaporComponentInstance
|
||||
return []
|
||||
},
|
||||
})
|
||||
const { render } = define({ render: () => createComponent(Comp) })
|
||||
|
|
|
@ -2,6 +2,7 @@ import {
|
|||
type VaporComponent,
|
||||
type VaporComponentInstance,
|
||||
createComponent,
|
||||
getExposed,
|
||||
mountComponent,
|
||||
unmountComponent,
|
||||
} from './component'
|
||||
|
@ -41,7 +42,7 @@ export const createVaporApp: CreateAppFunction<ParentNode, VaporComponent> = (
|
|||
comp,
|
||||
props,
|
||||
) => {
|
||||
if (!_createApp) _createApp = createAppAPI(mountApp, unmountApp, i => i)
|
||||
if (!_createApp) _createApp = createAppAPI(mountApp, unmountApp, getExposed)
|
||||
const app = _createApp(comp, props)
|
||||
|
||||
if (__DEV__) {
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
callWithErrorHandling,
|
||||
currentInstance,
|
||||
endMeasure,
|
||||
expose,
|
||||
nextUid,
|
||||
popWarningContext,
|
||||
pushWarningContext,
|
||||
|
@ -24,7 +25,12 @@ import {
|
|||
warn,
|
||||
} from '@vue/runtime-dom'
|
||||
import { type Block, insert, isBlock, remove } from './block'
|
||||
import { pauseTracking, proxyRefs, resetTracking } from '@vue/reactivity'
|
||||
import {
|
||||
markRaw,
|
||||
pauseTracking,
|
||||
proxyRefs,
|
||||
resetTracking,
|
||||
} from '@vue/reactivity'
|
||||
import { EMPTY_OBJ, invokeArrayFns, isFunction, isString } from '@vue/shared'
|
||||
import {
|
||||
type DynamicPropsSource,
|
||||
|
@ -55,7 +61,7 @@ export type VaporComponent = FunctionalVaporComponent | ObjectVaporComponent
|
|||
|
||||
export type VaporSetupFn = (
|
||||
props: any,
|
||||
ctx: SetupContext,
|
||||
ctx: Pick<VaporComponentInstance, 'slots' | 'attrs' | 'emit' | 'expose'>,
|
||||
) => Block | Record<string, any> | undefined
|
||||
|
||||
export type FunctionalVaporComponent = VaporSetupFn &
|
||||
|
@ -156,12 +162,10 @@ export function createComponent(
|
|||
pauseTracking()
|
||||
|
||||
const setupFn = isFunction(component) ? component : component.setup
|
||||
const setupContext = (instance.setupContext =
|
||||
setupFn && setupFn.length > 1 ? new SetupContext(instance) : null)
|
||||
const setupResult = setupFn
|
||||
? callWithErrorHandling(setupFn, instance, ErrorCodes.SETUP_FUNCTION, [
|
||||
instance.props,
|
||||
setupContext,
|
||||
instance,
|
||||
]) || EMPTY_OBJ
|
||||
: EMPTY_OBJ
|
||||
|
||||
|
@ -252,17 +256,22 @@ export class VaporComponentInstance implements GenericComponentInstance {
|
|||
|
||||
block: Block
|
||||
scope: EffectScope
|
||||
props: Record<string, any>
|
||||
attrs: Record<string, any>
|
||||
slots: StaticSlots
|
||||
exposed: Record<string, any> | null
|
||||
|
||||
rawProps: RawProps
|
||||
rawSlots: RawSlots
|
||||
|
||||
props: Record<string, any>
|
||||
attrs: Record<string, any>
|
||||
propsDefaults: Record<string, any> | null
|
||||
|
||||
slots: StaticSlots
|
||||
|
||||
emit: EmitFn
|
||||
emitted: Record<string, boolean> | null
|
||||
propsDefaults: Record<string, any> | null
|
||||
|
||||
expose: (exposed: Record<string, any>) => void
|
||||
exposed: Record<string, any> | null
|
||||
exposeProxy: Record<string, any> | null
|
||||
|
||||
// for useTemplateRef()
|
||||
refs: Record<string, any>
|
||||
|
@ -296,8 +305,6 @@ export class VaporComponentInstance implements GenericComponentInstance {
|
|||
ec?: LifecycleHook // LifecycleHooks.ERROR_CAPTURED
|
||||
sp?: LifecycleHook<() => Promise<unknown>> // LifecycleHooks.SERVER_PREFETCH
|
||||
|
||||
setupContext?: SetupContext | null
|
||||
|
||||
// dev only
|
||||
setupState?: Record<string, any>
|
||||
devtoolsRawSetupState?: any
|
||||
|
@ -336,8 +343,15 @@ export class VaporComponentInstance implements GenericComponentInstance {
|
|||
this.scope = new EffectScope(true)
|
||||
|
||||
this.emit = emit.bind(null, this)
|
||||
this.expose = expose.bind(null, this)
|
||||
this.refs = EMPTY_OBJ
|
||||
this.emitted = this.exposed = this.propsDefaults = this.suspense = null
|
||||
this.emitted =
|
||||
this.exposed =
|
||||
this.exposeProxy =
|
||||
this.propsDefaults =
|
||||
this.suspense =
|
||||
null
|
||||
|
||||
this.isMounted =
|
||||
this.isUnmounted =
|
||||
this.isUpdating =
|
||||
|
@ -383,22 +397,6 @@ export function isVaporComponent(
|
|||
return value instanceof VaporComponentInstance
|
||||
}
|
||||
|
||||
export class SetupContext {
|
||||
attrs: Record<string, any>
|
||||
emit: EmitFn
|
||||
slots: Readonly<StaticSlots>
|
||||
expose: (exposed?: Record<string, any>) => void
|
||||
|
||||
constructor(instance: VaporComponentInstance) {
|
||||
this.attrs = instance.attrs
|
||||
this.emit = instance.emit
|
||||
this.slots = instance.slots
|
||||
this.expose = (exposed = {}) => {
|
||||
instance.exposed = exposed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used when a component cannot be resolved at compile time
|
||||
* and needs rely on runtime resolution - where it might fallback to a plain
|
||||
|
@ -489,3 +487,14 @@ export function unmountComponent(
|
|||
remove(instance.block, parent)
|
||||
}
|
||||
}
|
||||
|
||||
export function getExposed(
|
||||
instance: GenericComponentInstance,
|
||||
): Record<string, any> | undefined {
|
||||
if (instance.exposed) {
|
||||
return (
|
||||
instance.exposeProxy ||
|
||||
(instance.exposeProxy = proxyRefs(markRaw(instance.exposed)))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue