Skip to content

Commit

Permalink
feat(runtime-core): explicit expose API
Browse files Browse the repository at this point in the history
  • Loading branch information
yyx990803 committed Nov 14, 2020
1 parent 15baaf1 commit 0e59770
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 7 deletions.
98 changes: 98 additions & 0 deletions packages/runtime-core/__tests__/apiExpose.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { nodeOps, render } from '@vue/runtime-test'
import { defineComponent, h, ref } from '../src'

describe('api: expose', () => {
test('via setup context', () => {
const Child = defineComponent({
render() {},
setup(_, { expose }) {
expose({
foo: ref(1),
bar: ref(2)
})
return {
bar: ref(3),
baz: ref(4)
}
}
})

const childRef = ref()
const Parent = {
setup() {
return () => h(Child, { ref: childRef })
}
}
const root = nodeOps.createElement('div')
render(h(Parent), root)
expect(childRef.value).toBeTruthy()
expect(childRef.value.foo).toBe(1)
expect(childRef.value.bar).toBe(2)
expect(childRef.value.baz).toBeUndefined()
})

test('via options', () => {
const Child = defineComponent({
render() {},
data() {
return {
foo: 1
}
},
setup() {
return {
bar: ref(2),
baz: ref(3)
}
},
expose: ['foo', 'bar']
})

const childRef = ref()
const Parent = {
setup() {
return () => h(Child, { ref: childRef })
}
}
const root = nodeOps.createElement('div')
render(h(Parent), root)
expect(childRef.value).toBeTruthy()
expect(childRef.value.foo).toBe(1)
expect(childRef.value.bar).toBe(2)
expect(childRef.value.baz).toBeUndefined()
})

test('options + context', () => {
const Child = defineComponent({
render() {},
expose: ['foo'],
data() {
return {
foo: 1
}
},
setup(_, { expose }) {
expose({
bar: ref(2)
})
return {
bar: ref(3),
baz: ref(4)
}
}
})

const childRef = ref()
const Parent = {
setup() {
return () => h(Child, { ref: childRef })
}
}
const root = nodeOps.createElement('div')
render(h(Parent), root)
expect(childRef.value).toBeTruthy()
expect(childRef.value.foo).toBe(1)
expect(childRef.value.bar).toBe(2)
expect(childRef.value.baz).toBeUndefined()
})
})
20 changes: 17 additions & 3 deletions packages/runtime-core/src/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ export interface ComponentInternalOptions {
export interface FunctionalComponent<P = {}, E extends EmitsOptions = {}>
extends ComponentInternalOptions {
// use of any here is intentional so it can be a valid JSX Element constructor
(props: P, ctx: SetupContext<E>): any
(props: P, ctx: Omit<SetupContext<E>, 'expose'>): any
props?: ComponentPropsOptions<P>
emits?: E | (keyof E)[]
inheritAttrs?: boolean
Expand Down Expand Up @@ -171,6 +171,7 @@ export interface SetupContext<E = EmitsOptions> {
attrs: Data
slots: Slots
emit: EmitFn<E>
expose: (exposed: Record<string, any>) => void
}

/**
Expand Down Expand Up @@ -270,6 +271,9 @@ export interface ComponentInternalInstance {
// main proxy that serves as the public instance (`this`)
proxy: ComponentPublicInstance | null

// exposed properties via expose()
exposed: Record<string, any> | null

/**
* alternative proxy used only for runtime-compiled render functions using
* `with` block
Expand Down Expand Up @@ -415,6 +419,7 @@ export function createComponentInstance(
update: null!, // will be set synchronously right after creation
render: null,
proxy: null,
exposed: null,
withProxy: null,
effects: null,
provides: parent ? parent.provides : Object.create(appContext.provides),
Expand Down Expand Up @@ -731,6 +736,13 @@ const attrHandlers: ProxyHandler<Data> = {
}

function createSetupContext(instance: ComponentInternalInstance): SetupContext {
const expose: SetupContext['expose'] = exposed => {
if (__DEV__ && instance.exposed) {
warn(`expose() should be called only once per setup().`)
}
instance.exposed = proxyRefs(exposed)
}

if (__DEV__) {
// We use getters in dev in case libs like test-utils overwrite instance
// properties (overwrites should not be done in prod)
Expand All @@ -743,13 +755,15 @@ function createSetupContext(instance: ComponentInternalInstance): SetupContext {
},
get emit() {
return (event: string, ...args: any[]) => instance.emit(event, ...args)
}
},
expose
})
} else {
return {
attrs: instance.attrs,
slots: instance.slots,
emit: instance.emit
emit: instance.emit,
expose
}
}
}
Expand Down
17 changes: 15 additions & 2 deletions packages/runtime-core/src/componentOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ import {
reactive,
ComputedGetter,
WritableComputedOptions,
toRaw
toRaw,
proxyRefs,
toRef
} from '@vue/reactivity'
import {
ComponentObjectPropsOptions,
Expand Down Expand Up @@ -110,6 +112,8 @@ export interface ComponentOptionsBase<
directives?: Record<string, Directive>
inheritAttrs?: boolean
emits?: (E | EE[]) & ThisType<void>
// TODO infer public instance type based on exposed keys
expose?: string[]
serverPrefetch?(): Promise<any>

// Internal ------------------------------------------------------------------
Expand Down Expand Up @@ -461,7 +465,9 @@ export function applyOptions(
render,
renderTracked,
renderTriggered,
errorCaptured
errorCaptured,
// public API
expose
} = options

const publicThis = instance.proxy!
Expand Down Expand Up @@ -736,6 +742,13 @@ export function applyOptions(
if (unmounted) {
onUnmounted(unmounted.bind(publicThis))
}

if (!asMixin && expose) {
const exposed = instance.exposed || (instance.exposed = proxyRefs({}))
expose.forEach(key => {
exposed[key] = toRef(publicThis, key as any)
})
}
}

function callSyncHook(
Expand Down
4 changes: 2 additions & 2 deletions packages/runtime-core/src/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -306,12 +306,12 @@ export const setRef = (
return
}

let value: ComponentPublicInstance | RendererNode | null
let value: ComponentPublicInstance | RendererNode | Record<string, any> | null
if (!vnode) {
value = null
} else {
if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
value = vnode.component!.proxy
value = vnode.component!.exposed || vnode.component!.proxy
} else {
value = vnode.el
}
Expand Down

0 comments on commit 0e59770

Please sign in to comment.