feat(runtime-core): explicit expose API
This commit is contained in:
		
							parent
							
								
									15baaf14f0
								
							
						
					
					
						commit
						0e59770b92
					
				
							
								
								
									
										98
									
								
								packages/runtime-core/__tests__/apiExpose.spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								packages/runtime-core/__tests__/apiExpose.spec.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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() | ||||
|   }) | ||||
| }) | ||||
| @ -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 | ||||
| @ -171,6 +171,7 @@ export interface SetupContext<E = EmitsOptions> { | ||||
|   attrs: Data | ||||
|   slots: Slots | ||||
|   emit: EmitFn<E> | ||||
|   expose: (exposed: Record<string, any>) => void | ||||
| } | ||||
| 
 | ||||
| /** | ||||
| @ -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 | ||||
| @ -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), | ||||
| @ -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)
 | ||||
| @ -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 | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -41,7 +41,9 @@ import { | ||||
|   reactive, | ||||
|   ComputedGetter, | ||||
|   WritableComputedOptions, | ||||
|   toRaw | ||||
|   toRaw, | ||||
|   proxyRefs, | ||||
|   toRef | ||||
| } from '@vue/reactivity' | ||||
| import { | ||||
|   ComponentObjectPropsOptions, | ||||
| @ -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 ------------------------------------------------------------------
 | ||||
| @ -461,7 +465,9 @@ export function applyOptions( | ||||
|     render, | ||||
|     renderTracked, | ||||
|     renderTriggered, | ||||
|     errorCaptured | ||||
|     errorCaptured, | ||||
|     // public API
 | ||||
|     expose | ||||
|   } = options | ||||
| 
 | ||||
|   const publicThis = instance.proxy! | ||||
| @ -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( | ||||
|  | ||||
| @ -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 | ||||
|     } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user