Просмотр исходного кода

chore: Merge branch 'feat/expose' into script-setup-2

Evan You 5 лет назад
Родитель
Сommit
468e0d95cf

+ 98 - 0
packages/runtime-core/__tests__/apiExpose.spec.ts

@@ -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()
+  })
+})

+ 17 - 3
packages/runtime-core/src/component.ts

@@ -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, P>): any
+  (props: P, ctx: Omit<SetupContext<E, P>, 'expose'>): any
   props?: ComponentPropsOptions<P>
   emits?: E | (keyof E)[]
   inheritAttrs?: boolean
@@ -172,6 +172,7 @@ export interface SetupContext<E = EmitsOptions, P = Data> {
   attrs: Data
   slots: Slots
   emit: EmitFn<E>
+  expose: (exposed: Record<string, any>) => void
 }
 
 /**
@@ -271,6 +272,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
@@ -416,6 +420,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),
@@ -732,6 +737,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)
@@ -747,14 +759,16 @@ function createSetupContext(instance: ComponentInternalInstance): SetupContext {
       },
       get emit() {
         return (event: string, ...args: any[]) => instance.emit(event, ...args)
-      }
+      },
+      expose
     })
   } else {
     return {
       props: instance.props,
       attrs: instance.attrs,
       slots: instance.slots,
-      emit: instance.emit
+      emit: instance.emit,
+      expose
     }
   }
 }

+ 15 - 2
packages/runtime-core/src/componentOptions.ts

@@ -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(

+ 2 - 2
packages/runtime-core/src/renderer.ts

@@ -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
     }

+ 3 - 5
packages/runtime-core/src/scheduler.ts

@@ -30,7 +30,7 @@ export type SchedulerCbs = SchedulerCb | SchedulerCb[]
 let isFlushing = false
 let isFlushPending = false
 
-const queue: (SchedulerJob | null)[] = []
+const queue: SchedulerJob[] = []
 let flushIndex = 0
 
 const pendingPreFlushCbs: SchedulerCb[] = []
@@ -87,7 +87,7 @@ function queueFlush() {
 export function invalidateJob(job: SchedulerJob) {
   const i = queue.indexOf(job)
   if (i > -1) {
-    queue[i] = null
+    queue.splice(i, 1)
   }
 }
 
@@ -205,9 +205,7 @@ function flushJobs(seen?: CountMap) {
   //    priority number)
   // 2. If a component is unmounted during a parent component's update,
   //    its update can be skipped.
-  // Jobs can never be null before flush starts, since they are only invalidated
-  // during execution of another flushed job.
-  queue.sort((a, b) => getId(a!) - getId(b!))
+  queue.sort((a, b) => getId(a) - getId(b))
 
   try {
     for (flushIndex = 0; flushIndex < queue.length; flushIndex++) {

+ 2 - 2
packages/runtime-dom/src/components/TransitionGroup.ts

@@ -18,9 +18,9 @@ import {
   setTransitionHooks,
   createVNode,
   onUpdated,
-  SetupContext
+  SetupContext,
+  toRaw
 } from '@vue/runtime-core'
-import { toRaw } from '@vue/reactivity'
 import { extend } from '@vue/shared'
 
 interface Position {

+ 6 - 4
packages/vue/src/dev.ts

@@ -8,10 +8,12 @@ export function initDev() {
   setDevtoolsHook(target.__VUE_DEVTOOLS_GLOBAL_HOOK__)
 
   if (__BROWSER__) {
-    console.info(
-      `You are running a development build of Vue.\n` +
-        `Make sure to use the production build (*.prod.js) when deploying for production.`
-    )
+    if (!__ESM_BUNDLER__) {
+      console.info(
+        `You are running a development build of Vue.\n` +
+          `Make sure to use the production build (*.prod.js) when deploying for production.`
+      )
+    }
 
     initCustomFormatter()
   }

Разница между файлами не показана из-за своего большого размера
+ 332 - 322
yarn.lock


Некоторые файлы не были показаны из-за большого количества измененных файлов