Przeglądaj źródła

wip: lifecycle api

Evan You 4 lat temu
rodzic
commit
1e4ac46fe5

+ 9 - 0
src/core/instance/lifecycle.ts

@@ -17,6 +17,8 @@ import {
   validateProp,
   invokeWithErrorHandling
 } from '../util/index'
+import { currentInstance, setCurrentInstance } from 'v3/currentInstance'
+import { syncObject } from 'v3/apiSetup'
 
 export let activeInstance: any = null
 export let isUpdatingChildComponent: boolean = false
@@ -270,6 +272,10 @@ export function updateChildComponent(
   // these are also reactive so they may trigger child update if the child
   // used them during render
   vm.$attrs = parentVnode.data.attrs || emptyObject
+  if (vm._attrsProxy) {
+    syncObject(vm._attrsProxy, vm.$attrs)
+  }
+
   vm.$listeners = listeners || emptyObject
 
   // update props
@@ -348,6 +354,8 @@ export function deactivateChildComponent(vm: Component, direct?: boolean) {
 export function callHook(vm: Component, hook: string) {
   // #7573 disable dep collection when invoking lifecycle hooks
   pushTarget()
+  const prev = currentInstance
+  setCurrentInstance(vm)
   const handlers = vm.$options[hook]
   const info = `${hook} hook`
   if (handlers) {
@@ -358,5 +366,6 @@ export function callHook(vm: Component, hook: string) {
   if (vm._hasHookEvent) {
     vm.$emit('hook:' + hook)
   }
+  setCurrentInstance(prev)
   popTarget()
 }

+ 63 - 53
src/core/instance/render.ts

@@ -15,6 +15,8 @@ import VNode, { createEmptyVNode } from '../vdom/vnode'
 
 import { isUpdatingChildComponent } from './lifecycle'
 import type { Component } from 'typescript/component'
+import { setCurrentInstance } from 'v3/currentInstance'
+import { syncObject } from 'v3/apiSetup'
 
 export function initRender(vm: Component) {
   vm._vnode = null // the root of the child tree
@@ -95,65 +97,73 @@ export function renderMixin(Vue: Component) {
   Vue.prototype._render = function (): VNode {
     const vm: Component = this
     const { render, _parentVnode } = vm.$options
+    setCurrentInstance(vm)
 
-    if (_parentVnode) {
-      vm.$scopedSlots = normalizeScopedSlots(
-        _parentVnode.data!.scopedSlots,
-        vm.$slots,
-        vm.$scopedSlots
-      )
-    }
-
-    // set parent vnode. this allows render functions to have access
-    // to the data on the placeholder node.
-    vm.$vnode = _parentVnode!
-    // render self
-    let vnode
     try {
-      // There's no need to maintain a stack because all render fns are called
-      // separately from one another. Nested component's render fns are called
-      // when parent component is patched.
-      currentRenderingInstance = vm
-      vnode = render.call(vm._renderProxy, vm.$createElement)
-    } catch (e: any) {
-      handleError(e, vm, `render`)
-      // return error render result,
-      // or previous vnode to prevent render error causing blank component
-      /* istanbul ignore else */
-      if (__DEV__ && vm.$options.renderError) {
-        try {
-          vnode = vm.$options.renderError.call(
-            vm._renderProxy,
-            vm.$createElement,
-            e
-          )
-        } catch (e: any) {
-          handleError(e, vm, `renderError`)
+      if (_parentVnode) {
+        vm.$scopedSlots = normalizeScopedSlots(
+          _parentVnode.data!.scopedSlots,
+          vm.$slots,
+          vm.$scopedSlots
+        )
+        if (vm._slotsProxy) {
+          syncObject(vm._slotsProxy, vm.$scopedSlots)
+        }
+      }
+
+      // set parent vnode. this allows render functions to have access
+      // to the data on the placeholder node.
+      vm.$vnode = _parentVnode!
+      // render self
+      let vnode
+      try {
+        // There's no need to maintain a stack because all render fns are called
+        // separately from one another. Nested component's render fns are called
+        // when parent component is patched.
+        currentRenderingInstance = vm
+        vnode = render.call(vm._renderProxy, vm.$createElement)
+      } catch (e: any) {
+        handleError(e, vm, `render`)
+        // return error render result,
+        // or previous vnode to prevent render error causing blank component
+        /* istanbul ignore else */
+        if (__DEV__ && vm.$options.renderError) {
+          try {
+            vnode = vm.$options.renderError.call(
+              vm._renderProxy,
+              vm.$createElement,
+              e
+            )
+          } catch (e: any) {
+            handleError(e, vm, `renderError`)
+            vnode = vm._vnode
+          }
+        } else {
           vnode = vm._vnode
         }
-      } else {
-        vnode = vm._vnode
+      } finally {
+        currentRenderingInstance = null
       }
-    } finally {
-      currentRenderingInstance = null
-    }
-    // if the returned array contains only a single node, allow it
-    if (isArray(vnode) && vnode.length === 1) {
-      vnode = vnode[0]
-    }
-    // return empty vnode in case the render function errored out
-    if (!(vnode instanceof VNode)) {
-      if (__DEV__ && isArray(vnode)) {
-        warn(
-          'Multiple root nodes returned from render function. Render function ' +
-            'should return a single root node.',
-          vm
-        )
+      // if the returned array contains only a single node, allow it
+      if (isArray(vnode) && vnode.length === 1) {
+        vnode = vnode[0]
       }
-      vnode = createEmptyVNode()
+      // return empty vnode in case the render function errored out
+      if (!(vnode instanceof VNode)) {
+        if (__DEV__ && isArray(vnode)) {
+          warn(
+            'Multiple root nodes returned from render function. Render function ' +
+              'should return a single root node.',
+            vm
+          )
+        }
+        vnode = createEmptyVNode()
+      }
+      // set parent
+      vnode.parent = _parentVnode
+      return vnode
+    } finally {
+      setCurrentInstance()
     }
-    // set parent
-    vnode.parent = _parentVnode
-    return vnode
   }
 }

+ 5 - 0
src/core/instance/state.ts

@@ -2,6 +2,7 @@ import config from '../config'
 import Watcher from '../observer/watcher'
 import Dep, { pushTarget, popTarget } from '../observer/dep'
 import { isUpdatingChildComponent } from './lifecycle'
+import { initSetup } from 'v3/apiSetup'
 
 import {
   set,
@@ -50,6 +51,10 @@ export function initState(vm: Component) {
   vm._watchers = []
   const opts = vm.$options
   if (opts.props) initProps(vm, opts.props)
+
+  // Composition API
+  initSetup(vm)
+
   if (opts.methods) initMethods(vm, opts.methods)
   if (opts.data) {
     initData(vm)

+ 2 - 2
src/core/util/error.ts

@@ -41,11 +41,11 @@ export function invokeWithErrorHandling(
   let res
   try {
     res = args ? handler.apply(context, args) : handler.call(context)
-    if (res && !res._isVue && isPromise(res) && !res._handled) {
+    if (res && !res._isVue && isPromise(res) && !(res as any)._handled) {
       res.catch(e => handleError(e, vm, info + ` (Promise/async)`))
       // issue #9511
       // avoid catch triggering multiple times when nested calls
-      res._handled = true
+      ;(res as any)._handled = true
     }
   } catch (e: any) {
     handleError(e, vm, info)

+ 2 - 2
src/core/util/options.ts

@@ -148,7 +148,7 @@ strats.data = function (
 /**
  * Hooks and props are merged as arrays.
  */
-function mergeHook(
+export function mergeLifecycleHook(
   parentVal: Array<Function> | null,
   childVal: Function | Array<Function> | null
 ): Array<Function> | null {
@@ -173,7 +173,7 @@ function dedupeHooks(hooks: any) {
 }
 
 LIFECYCLE_HOOKS.forEach(hook => {
-  strats[hook] = mergeHook
+  strats[hook] = mergeLifecycleHook
 })
 
 /**

+ 4 - 4
src/shared/util.ts

@@ -71,7 +71,7 @@ export function isValidArrayIndex(val: any): boolean {
   return n >= 0 && Math.floor(n) === n && isFinite(val)
 }
 
-export function isPromise(val: any): boolean {
+export function isPromise(val: any): val is Promise<any> {
   return (
     isDef(val) &&
     typeof val.then === 'function' &&
@@ -340,14 +340,14 @@ export function looseIndexOf(arr: Array<unknown>, val: unknown): number {
 /**
  * Ensure a function is called only once.
  */
-export function once(fn: Function): Function {
+export function once<T extends (...args: any[]) => any>(fn: T): T {
   let called = false
   return function () {
     if (!called) {
       called = true
-      fn.apply(this, arguments)
+      fn.apply(this, arguments as any)
     }
-  }
+  } as any
 }
 
 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is#polyfill

+ 45 - 1
src/v3/apiLifecycle.ts

@@ -1 +1,45 @@
-export function onMounted() {}
+import { Component } from '../../typescript/component'
+import { mergeLifecycleHook, warn } from '../core/util'
+import { currentInstance } from './currentInstance'
+
+function createLifeCycle(hookName: string) {
+  return (fn: () => void, target: Component | null = currentInstance) => {
+    if (!target) {
+      __DEV__ &&
+        warn(
+          `${formatName(
+            hookName
+          )} is called when there is no active component instance to be ` +
+            `associated with. ` +
+            `Lifecycle injection APIs can only be used during execution of setup().`
+        )
+      return
+    }
+    return injectHook(target, hookName, fn)
+  }
+}
+
+function formatName(name: string) {
+  if (name === 'beforeDestroy') {
+    name = 'beforeUnmount'
+  } else if (name === 'destroyed') {
+    name = 'unmounted'
+  }
+  return `on${name[0].toUpperCase() + name.slice(1)}`
+}
+
+function injectHook(instance: Component, hookName: string, fn: () => void) {
+  const options = instance.$options
+  options[hookName] = mergeLifecycleHook(options[hookName], fn)
+}
+
+export const onBeforeMount = createLifeCycle('beforeMount')
+export const onMounted = createLifeCycle('mounted')
+export const onBeforeUpdate = createLifeCycle('beforeUpdate')
+export const onUpdated = createLifeCycle('updated')
+export const onBeforeUnmount = createLifeCycle('beforeDestroy')
+export const onUnmounted = createLifeCycle('destroyed')
+export const onErrorCaptured = createLifeCycle('errorCaptured')
+export const onActivated = createLifeCycle('activated')
+export const onDeactivated = createLifeCycle('deactivated')
+export const onServerPrefetch = createLifeCycle('serverPrefetch')

+ 114 - 0
src/v3/apiSetup.ts

@@ -0,0 +1,114 @@
+import { Component } from 'typescript/component'
+import type { SetupContext } from 'typescript/options'
+import { invokeWithErrorHandling, isReserved, warn } from '../core/util'
+import VNode from '../core/vdom/vnode'
+import { bind, isObject } from '../shared/util'
+import { currentInstance, setCurrentInstance } from './currentInstance'
+import { isRef } from './reactivity/ref'
+
+export function initSetup(vm: Component) {
+  const options = vm.$options
+  const setup = options.setup
+  if (setup) {
+    const ctx = {
+      get attrs() {
+        return initAttrsProxy(vm)
+      },
+      get slots() {
+        return initSlotsProxy(vm)
+      },
+      emit: bind(vm.$emit, vm) as any
+    }
+
+    setCurrentInstance(vm)
+    const setupResult = invokeWithErrorHandling(
+      setup,
+      null,
+      [vm._props, ctx],
+      vm,
+      `setup`
+    )
+    setCurrentInstance()
+
+    if (typeof setupResult === 'function') {
+      // render function
+      // @ts-ignore
+      options.render = setupResult
+    } else if (isObject(setupResult)) {
+      // bindings
+      if (__DEV__ && setupResult instanceof VNode) {
+        warn(
+          `setup() should not return VNodes directly - ` +
+            `return a render function instead.`
+        )
+      }
+      vm._setupState = setupResult
+      for (const key in setupResult) {
+        if (!isReserved(key)) {
+          proxySetupProperty(vm, setupResult, key)
+        }
+      }
+    } else if (__DEV__ && setupResult !== undefined) {
+      warn(
+        `setup() should return an object. Received: ${
+          setupResult === null ? 'null' : typeof setupResult
+        }`
+      )
+    }
+  }
+}
+
+function proxySetupProperty(
+  vm: Component,
+  setupResult: Record<string, any>,
+  key: string
+) {
+  const raw = setupResult[key]
+  const unwrap = isRef(raw)
+  Object.defineProperty(vm, key, {
+    enumerable: true,
+    configurable: true,
+    get: unwrap ? () => raw.value : () => setupResult[key],
+    set: unwrap ? v => (raw.value = v) : v => (setupResult[key] = v)
+  })
+}
+
+function initAttrsProxy(vm: Component) {
+  if (!vm._attrsProxy) {
+    syncObject((vm._attrsProxy = {}), vm.$attrs)
+  }
+  return vm._attrsProxy
+}
+
+function initSlotsProxy(vm: Component) {
+  if (!vm._slotsProxy) {
+    syncObject((vm._slotsProxy = {}), vm.$scopedSlots)
+  }
+  return vm._slotsProxy
+}
+
+export function syncObject(to: any, from: any) {
+  for (const key in from) {
+    to[key] = from[key]
+  }
+  for (const key in to) {
+    if (!(key in from)) {
+      delete to[key]
+    }
+  }
+}
+
+export function useSlots(): SetupContext['slots'] {
+  return getContext().slots
+}
+
+export function useAttrs(): SetupContext['attrs'] {
+  return getContext().attrs
+}
+
+function getContext(): SetupContext {
+  if (__DEV__ && !currentInstance) {
+    warn(`useContext() called without active instance.`)
+  }
+  return currentInstance!.setupContext
+}

+ 1 - 1
src/v3/currentInstance.ts

@@ -16,6 +16,6 @@ export function getCurrentInstance(): { proxy: Component } | null {
 /**
  * @private
  */
-export function setCurrentInstance(vm: Component | null) {
+export function setCurrentInstance(vm: Component | null = null) {
   currentInstance = vm
 }

+ 1 - 10
src/v3/h.ts

@@ -1,20 +1,11 @@
 import { createElement } from '../core/vdom/create-element'
-import VNode from 'core/vdom/vnode'
-import { VNodeData, VNodeChildren } from 'typescript/vnode'
 import { currentInstance } from './currentInstance'
 import { warn } from 'core/util'
 
 /**
- * @private this function needs manual type declaration because it relies
+ * @private this function needs manual public type declaration because it relies
  * on previously manually authored types from Vue 2
  */
-export function h(type: any, children?: VNodeChildren): VNode
-export function h(
-  type: any,
-  props?: VNodeData | null,
-  children?: VNodeChildren
-): VNode
-
 export function h(type: any, props?: any, children?: any) {
   if (!currentInstance) {
     __DEV__ &&

+ 3 - 0
src/v3/index.ts

@@ -61,3 +61,6 @@ export { TrackOpTypes, TriggerOpTypes } from './reactivity/operations'
 
 export { h } from './h'
 export { getCurrentInstance } from './currentInstance'
+export { useSlots, useAttrs } from './apiSetup'
+
+export * from './apiLifecycle'

+ 284 - 0
test/unit/features/v3/apiLifecycle.spec.ts

@@ -0,0 +1,284 @@
+import Vue from 'vue'
+import {
+  h,
+  onBeforeMount,
+  onMounted,
+  ref,
+  onBeforeUpdate,
+  onUpdated,
+  onBeforeUnmount,
+  onUnmounted
+} from 'v3'
+import { nextTick } from 'core/util'
+
+describe('api: lifecycle hooks', () => {
+  it('onBeforeMount', () => {
+    const fn = vi.fn(() => {
+      // should be called before root is replaced
+      expect(vm.$el).toBeUndefined()
+    })
+
+    const Comp = {
+      setup() {
+        onBeforeMount(fn)
+        return () => h('div', 'hello')
+      }
+    }
+    const vm = new Vue(Comp)
+    vm.$mount()
+    expect(fn).toHaveBeenCalledTimes(1)
+    expect(vm.$el.innerHTML).toBe(`hello`)
+  })
+
+  it('onMounted', () => {
+    const fn = vi.fn(() => {
+      // should be called after inner div is rendered
+      expect(vm.$el.outerHTML).toBe(`<div></div>`)
+    })
+
+    const Comp = {
+      setup() {
+        onMounted(fn)
+        return () => h('div')
+      }
+    }
+    const vm = new Vue(Comp)
+    vm.$mount()
+    expect(fn).toHaveBeenCalledTimes(1)
+  })
+
+  it('onBeforeUpdate', async () => {
+    const count = ref(0)
+    const fn = vi.fn(() => {
+      // should be called before inner div is updated
+      expect(vm.$el.outerHTML).toBe(`<div>0</div>`)
+    })
+
+    const Comp = {
+      setup() {
+        onBeforeUpdate(fn)
+        return () => h('div', count.value)
+      }
+    }
+    const vm = new Vue(Comp).$mount()
+
+    count.value++
+    await nextTick()
+    expect(fn).toHaveBeenCalledTimes(1)
+    expect(vm.$el.outerHTML).toBe(`<div>1</div>`)
+  })
+
+  it('state mutation in onBeforeUpdate', async () => {
+    const count = ref(0)
+    const fn = vi.fn(() => {
+      // should be called before inner div is updated
+      expect(vm.$el.outerHTML).toBe(`<div>0</div>`)
+      count.value++
+    })
+    const renderSpy = vi.fn()
+
+    const Comp = {
+      setup() {
+        onBeforeUpdate(fn)
+        return () => {
+          renderSpy()
+          return h('div', count.value)
+        }
+      }
+    }
+    const vm = new Vue(Comp).$mount()
+    expect(renderSpy).toHaveBeenCalledTimes(1)
+
+    count.value++
+    await nextTick()
+    expect(fn).toHaveBeenCalledTimes(1)
+    expect(renderSpy).toHaveBeenCalledTimes(2)
+    expect(vm.$el.outerHTML).toBe(`<div>2</div>`)
+  })
+
+  it('onUpdated', async () => {
+    const count = ref(0)
+    const fn = vi.fn(() => {
+      // should be called after inner div is updated
+      expect(vm.$el.outerHTML).toBe(`<div>1</div>`)
+    })
+
+    const Comp = {
+      setup() {
+        onUpdated(fn)
+        return () => h('div', count.value)
+      }
+    }
+    const vm = new Vue(Comp).$mount()
+
+    count.value++
+    await nextTick()
+    expect(fn).toHaveBeenCalledTimes(1)
+  })
+
+  it('onBeforeUnmount', async () => {
+    const toggle = ref(true)
+    const root = document.createElement('div')
+    const fn = vi.fn(() => {
+      // should be called before inner div is removed
+      expect(root.outerHTML).toBe(`<div></div>`)
+    })
+
+    const Comp = {
+      setup() {
+        return () => (toggle.value ? h(Child) : null)
+      }
+    }
+
+    const Child = {
+      setup() {
+        onBeforeUnmount(fn)
+        return () => h('div')
+      }
+    }
+
+    new Vue(Comp).$mount(root)
+
+    toggle.value = false
+    await nextTick()
+    expect(fn).toHaveBeenCalledTimes(1)
+  })
+
+  it('onUnmounted', async () => {
+    const toggle = ref(true)
+    const fn = vi.fn(() => {
+      // @discrepancy should be called after inner div is removed
+      // expect(vm.$el.outerHTML).toBe(`<span></span>`)
+    })
+
+    const Comp = {
+      setup() {
+        return () => (toggle.value ? h(Child) : h('span'))
+      }
+    }
+
+    const Child = {
+      setup() {
+        onUnmounted(fn)
+        return () => h('div')
+      }
+    }
+
+    new Vue(Comp).$mount()
+
+    toggle.value = false
+    await nextTick()
+    expect(fn).toHaveBeenCalledTimes(1)
+  })
+
+  it('onBeforeUnmount in onMounted', async () => {
+    const toggle = ref(true)
+    const fn = vi.fn(() => {
+      // should be called before inner div is removed
+      expect(vm.$el.outerHTML).toBe(`<div></div>`)
+    })
+
+    const Comp = {
+      setup() {
+        return () => (toggle.value ? h(Child) : null)
+      }
+    }
+
+    const Child = {
+      setup() {
+        onMounted(() => {
+          onBeforeUnmount(fn)
+        })
+        return () => h('div')
+      }
+    }
+
+    const vm = new Vue(Comp).$mount()
+
+    toggle.value = false
+    await nextTick()
+    expect(fn).toHaveBeenCalledTimes(1)
+  })
+
+  it('lifecycle call order', async () => {
+    const count = ref(0)
+    const calls: string[] = []
+
+    const Root = {
+      setup() {
+        onBeforeMount(() => calls.push('root onBeforeMount'))
+        onMounted(() => calls.push('root onMounted'))
+        onBeforeUpdate(() => calls.push('root onBeforeUpdate'))
+        onUpdated(() => calls.push('root onUpdated'))
+        onBeforeUnmount(() => calls.push('root onBeforeUnmount'))
+        onUnmounted(() => calls.push('root onUnmounted'))
+        return () => h(Mid, { props: { count: count.value } })
+      }
+    }
+
+    const Mid = {
+      props: ['count'],
+      setup(props: any) {
+        onBeforeMount(() => calls.push('mid onBeforeMount'))
+        onMounted(() => calls.push('mid onMounted'))
+        onBeforeUpdate(() => calls.push('mid onBeforeUpdate'))
+        onUpdated(() => calls.push('mid onUpdated'))
+        onBeforeUnmount(() => calls.push('mid onBeforeUnmount'))
+        onUnmounted(() => calls.push('mid onUnmounted'))
+        return () => h(Child, { props: { count: props.count } })
+      }
+    }
+
+    const Child = {
+      props: ['count'],
+      setup(props: any) {
+        onBeforeMount(() => calls.push('child onBeforeMount'))
+        onMounted(() => calls.push('child onMounted'))
+        onBeforeUpdate(() => calls.push('child onBeforeUpdate'))
+        onUpdated(() => calls.push('child onUpdated'))
+        onBeforeUnmount(() => calls.push('child onBeforeUnmount'))
+        onUnmounted(() => calls.push('child onUnmounted'))
+        return () => h('div', props.count)
+      }
+    }
+
+    // mount
+    const vm = new Vue(Root)
+    vm.$mount()
+    expect(calls).toEqual([
+      'root onBeforeMount',
+      'mid onBeforeMount',
+      'child onBeforeMount',
+      'child onMounted',
+      'mid onMounted',
+      'root onMounted'
+    ])
+
+    calls.length = 0
+
+    // update
+    count.value++
+    await nextTick()
+    expect(calls).toEqual([
+      'root onBeforeUpdate',
+      'mid onBeforeUpdate',
+      'child onBeforeUpdate',
+      'child onUpdated',
+      'mid onUpdated',
+      'root onUpdated'
+    ])
+
+    calls.length = 0
+
+    // unmount
+    vm.$destroy()
+    expect(calls).toEqual([
+      'root onBeforeUnmount',
+      'mid onBeforeUnmount',
+      'child onBeforeUnmount',
+      'child onUnmounted',
+      'mid onUnmounted',
+      'root onUnmounted'
+    ])
+  })
+})

+ 0 - 0
test/unit/features/v3/apiSetup.spec.ts


+ 9 - 8
test/unit/features/v3/apiWatch.spec.ts

@@ -7,14 +7,15 @@ import {
   reactive,
   computed,
   ref,
-  DebuggerEvent,
-  TrackOpTypes,
-  TriggerOpTypes,
+  // DebuggerEvent,
+  // TrackOpTypes,
+  // TriggerOpTypes,
   triggerRef,
   shallowRef,
   Ref,
   h,
-  getCurrentInstance
+  getCurrentInstance,
+  onMounted
   // effectScope
 } from 'v3'
 import { nextTick } from 'core/util'
@@ -600,11 +601,11 @@ describe('api: watch', () => {
   // #1852
   it('flush: post watcher should fire after template refs updated', async () => {
     const toggle = ref(false)
-    let dom: TestElement | null = null
+    let dom: HTMLElement | null = null
 
     const App = {
       setup() {
-        const domRef = ref<TestElement | null>(null)
+        const domRef = ref<any>(null)
 
         watch(
           toggle,
@@ -625,7 +626,7 @@ describe('api: watch', () => {
 
     toggle.value = true
     await nextTick()
-    expect(dom!.tag).toBe('p')
+    expect(dom!.tagName).toBe('P')
   })
 
   it('deep', async () => {
@@ -924,7 +925,7 @@ describe('api: watch', () => {
 
   // TODO
   // https://github.com/vuejs/core/issues/2381
-  test.skip('$watch should always register its effects with its own instance', async () => {
+  test('$watch should always register its effects with its own instance', async () => {
     let instance: Component | null
     let _show: Ref<boolean>
 

+ 0 - 1
types/v3.d.ts

@@ -5,7 +5,6 @@ export interface SetupContext {
   attrs: Record<string, any>
   slots: Record<string, (() => VNode[]) | undefined>
   emit: (event: string, ...args: any[]) => any
-  expose: (exposed?: Record<string, any>) => void
 }
 
 export function getCurrentInstance(): { proxy: Vue } | null

+ 6 - 1
typescript/component.d.ts

@@ -38,7 +38,7 @@ export declare class Component {
     [key: string]: Component | Element | Array<Component | Element> | undefined
   }
   $slots: { [key: string]: Array<VNode> }
-  $scopedSlots: { [key: string]: () => VNodeChildren }
+  $scopedSlots: { [key: string]: () => VNode[] | undefined }
   $vnode: VNode // the placeholder node for the component in parent's render tree
   $attrs: { [key: string]: string }
   $listeners: Record<string, Function | Array<Function>>
@@ -97,6 +97,11 @@ export declare class Component {
   _provided?: Record<string, any>
   // _virtualComponents?: { [key: string]: Component };
 
+  // @v3
+  _setupState?: Record<string, any>
+  _attrsProxy?: Record<string, any>
+  _slotsProxy?: Record<string, () => VNode[]>
+
   // private methods
 
   // lifecycle

+ 2 - 3
typescript/options.d.ts

@@ -13,14 +13,13 @@ type InjectKey = string | Symbol
 
 declare interface SetupContext {
   attrs: Record<string, any>
-  slots: Record<string, (() => VNode[]) | undefined>
+  slots: Record<string, () => VNode[]>
   emit: (event: string, ...args: any[]) => any
-  expose: (exposed?: Record<string, any>) => void
 }
 
 declare type ComponentOptions = {
   // v3
-  setup(props: Record<string, any>, ctx: SetupContext): any
+  setup?: (props: Record<string, any>, ctx: SetupContext) => unknown
 
   [key: string]: any
 

+ 1 - 1
typescript/vnode.d.ts

@@ -2,7 +2,7 @@ import VNode from '../src/core/vdom/vnode'
 import { Component } from './component'
 
 declare type VNodeChildren =
-  | Array<null | VNode | string | VNodeChildren>
+  | Array<null | VNode | string | number | VNodeChildren>
   | string
 
 declare type VNodeComponentOptions = {