refactor: reorganize component related code and types

This commit is contained in:
Evan You 2019-09-06 11:19:22 -04:00
parent 4c075803b5
commit 0f25c29119
10 changed files with 218 additions and 200 deletions

View File

@ -1,4 +1,4 @@
import { createComponent } from '../src/component' import { createComponent } from '../src/apiCreateComponent'
import { ref } from '@vue/reactivity' import { ref } from '@vue/reactivity'
import { PropType } from '../src/componentProps' import { PropType } from '../src/componentProps'
import { h } from '../src/h' import { h } from '../src/h'

View File

@ -1,10 +1,6 @@
import { import { Component, Data, ComponentInstance } from './component'
ComponentOptions, import { ComponentOptions } from './componentOptions'
Component, import { ComponentRenderProxy } from './componentProxy'
ComponentRenderProxy,
Data,
ComponentInstance
} from './component'
import { Directive } from './directives' import { Directive } from './directives'
import { HostNode, RootRenderFunction } from './createRenderer' import { HostNode, RootRenderFunction } from './createRenderer'
import { InjectionKey } from './apiInject' import { InjectionKey } from './apiInject'

View File

@ -0,0 +1,81 @@
import {
ComputedOptions,
MethodOptions,
ComponentOptionsWithoutProps,
ComponentOptionsWithArrayProps,
ComponentOptionsWithProps
} from './componentOptions'
import { SetupContext } from './component'
import { VNodeChild } from './vnode'
import { ComponentRenderProxy } from './componentProxy'
import { ExtractPropTypes } from './componentProps'
import { isFunction } from '@vue/shared'
// overload 1: direct setup function
// (uses user defined props interface)
export function createComponent<Props>(
setup: (props: Props, ctx: SetupContext) => object | (() => VNodeChild)
): (props: Props) => any
// overload 2: object format with no props
// (uses user defined props interface)
// return type is for Vetur and TSX support
export function createComponent<
Props,
RawBindings,
D,
C extends ComputedOptions = {},
M extends MethodOptions = {}
>(
options: ComponentOptionsWithoutProps<Props, RawBindings, D, C, M>
): {
new (): ComponentRenderProxy<Props, RawBindings, D, C, M>
}
// overload 3: object format with array props declaration
// props inferred as { [key in PropNames]?: unknown }
// return type is for Vetur and TSX support
export function createComponent<
PropNames extends string,
RawBindings,
D,
C extends ComputedOptions = {},
M extends MethodOptions = {}
>(
options: ComponentOptionsWithArrayProps<PropNames, RawBindings, D, C, M>
): {
new (): ComponentRenderProxy<
{ [key in PropNames]?: unknown },
RawBindings,
D,
C,
M
>
}
// overload 4: object format with object props declaration
// see `ExtractPropTypes` in ./componentProps.ts
export function createComponent<
PropsOptions,
RawBindings,
D,
C extends ComputedOptions = {},
M extends MethodOptions = {}
>(
options: ComponentOptionsWithProps<PropsOptions, RawBindings, D, C, M>
): {
// for Vetur and TSX support
new (): ComponentRenderProxy<
ExtractPropTypes<PropsOptions>,
RawBindings,
D,
C,
M,
ExtractPropTypes<PropsOptions, false>
>
}
// implementation, close to no-op
export function createComponent(options: any) {
return isFunction(options) ? { setup: options } : (options as any)
}

View File

@ -2,9 +2,9 @@ import {
ComponentInstance, ComponentInstance,
LifecycleHooks, LifecycleHooks,
currentInstance, currentInstance,
setCurrentInstance, setCurrentInstance
ComponentRenderProxy
} from './component' } from './component'
import { ComponentRenderProxy } from './componentProxy'
import { callWithAsyncErrorHandling, ErrorTypeStrings } from './errorHandling' import { callWithAsyncErrorHandling, ErrorTypeStrings } from './errorHandling'
import { warn } from './warning' import { warn } from './warning'
import { capitalize } from '@vue/shared' import { capitalize } from '@vue/shared'

View File

@ -1,15 +1,7 @@
import { VNode, normalizeVNode, VNodeChild, createVNode, Empty } from './vnode' import { VNode, normalizeVNode, VNodeChild, createVNode, Empty } from './vnode'
import { ReactiveEffect, UnwrapRef, reactive, readonly } from '@vue/reactivity' import { ReactiveEffect, reactive, readonly } from '@vue/reactivity'
import { import { RenderProxyHandlers, ComponentRenderProxy } from './componentProxy'
EMPTY_OBJ, import { ComponentPropsOptions } from './componentProps'
isFunction,
capitalize,
NOOP,
isArray,
isObject
} from '@vue/shared'
import { RenderProxyHandlers } from './componentProxy'
import { ComponentPropsOptions, ExtractPropTypes } from './componentProps'
import { Slots } from './componentSlots' import { Slots } from './componentSlots'
import { PatchFlags } from './patchFlags' import { PatchFlags } from './patchFlags'
import { ShapeFlags } from './shapeFlags' import { ShapeFlags } from './shapeFlags'
@ -24,100 +16,20 @@ import { AppContext, createAppContext } from './apiApp'
import { Directive } from './directives' import { Directive } from './directives'
import { import {
applyOptions, applyOptions,
LegacyOptions,
resolveAsset, resolveAsset,
ComputedOptions, ComponentOptions
MethodOptions, } from './componentOptions'
ExtracComputedReturns import {
} from './apiOptions' EMPTY_OBJ,
isFunction,
capitalize,
NOOP,
isArray,
isObject
} from '@vue/shared'
export type Data = { [key: string]: unknown } export type Data = { [key: string]: unknown }
// public properties exposed on the proxy, which is used as the render context
// in templates (as `this` in the render option)
export type ComponentRenderProxy<
P = {},
B = {},
D = {},
C = {},
M = {},
PublicProps = P
> = {
$data: D
$props: PublicProps
$attrs: Data
$refs: Data
$slots: Data
$root: ComponentInstance | null
$parent: ComponentInstance | null
$emit: (event: string, ...args: unknown[]) => void
} & P &
UnwrapRef<B> &
D &
ExtracComputedReturns<C> &
M
interface ComponentOptionsBase<
Props,
RawBindings,
D,
C extends ComputedOptions,
M extends MethodOptions
> extends LegacyOptions<Props, RawBindings, D, C, M> {
setup?: (
this: null,
props: Props,
ctx: SetupContext
) => RawBindings | (() => VNodeChild) | void
name?: string
template?: string
// Note: we are intentionally using the signature-less `Function` type here
// since any type with signature will cause the whole inference to fail when
// the return expression contains reference to `this`.
// Luckily `render()` doesn't need any arguments nor does it care about return
// type.
render?: Function
components?: Record<string, Component>
directives?: Record<string, Directive>
}
export type ComponentOptionsWithoutProps<
Props = {},
RawBindings = {},
D = {},
C extends ComputedOptions = {},
M extends MethodOptions = {}
> = ComponentOptionsBase<Props, RawBindings, D, C, M> & {
props?: undefined
} & ThisType<ComponentRenderProxy<Props, RawBindings, D, C, M>>
export type ComponentOptionsWithArrayProps<
PropNames extends string = string,
RawBindings = {},
D = {},
C extends ComputedOptions = {},
M extends MethodOptions = {},
Props = { [key in PropNames]?: unknown }
> = ComponentOptionsBase<Props, RawBindings, D, C, M> & {
props: PropNames[]
} & ThisType<ComponentRenderProxy<Props, RawBindings, D, C, M>>
export type ComponentOptionsWithProps<
PropsOptions = ComponentPropsOptions,
RawBindings = {},
D = {},
C extends ComputedOptions = {},
M extends MethodOptions = {},
Props = ExtractPropTypes<PropsOptions>
> = ComponentOptionsBase<Props, RawBindings, D, C, M> & {
props: PropsOptions
} & ThisType<ComponentRenderProxy<Props, RawBindings, D, C, M>>
export type ComponentOptions =
| ComponentOptionsWithoutProps
| ComponentOptionsWithProps
| ComponentOptionsWithArrayProps
export interface FunctionalComponent<P = {}> { export interface FunctionalComponent<P = {}> {
(props: P, ctx: SetupContext): VNodeChild (props: P, ctx: SetupContext): VNodeChild
props?: ComponentPropsOptions<P> props?: ComponentPropsOptions<P>
@ -146,7 +58,7 @@ export const enum LifecycleHooks {
type Emit = ((event: string, ...args: unknown[]) => void) type Emit = ((event: string, ...args: unknown[]) => void)
interface SetupContext { export interface SetupContext {
attrs: Data attrs: Data
slots: Slots slots: Slots
emit: Emit emit: Emit
@ -201,72 +113,6 @@ export interface ComponentInstance {
[LifecycleHooks.ERROR_CAPTURED]: LifecycleHook [LifecycleHooks.ERROR_CAPTURED]: LifecycleHook
} }
// createComponent
// overload 1: direct setup function
// (uses user defined props interface)
export function createComponent<Props>(
setup: (props: Props, ctx: SetupContext) => object | (() => VNodeChild)
): (props: Props) => any
// overload 2: object format with no props
// (uses user defined props interface)
// return type is for Vetur and TSX support
export function createComponent<
Props,
RawBindings,
D,
C extends ComputedOptions = {},
M extends MethodOptions = {}
>(
options: ComponentOptionsWithoutProps<Props, RawBindings, D, C, M>
): {
new (): ComponentRenderProxy<Props, RawBindings, D, C, M>
}
// overload 3: object format with array props declaration
// props inferred as { [key in PropNames]?: unknown }
// return type is for Vetur and TSX support
export function createComponent<
PropNames extends string,
RawBindings,
D,
C extends ComputedOptions = {},
M extends MethodOptions = {}
>(
options: ComponentOptionsWithArrayProps<PropNames, RawBindings, D, C, M>
): {
new (): ComponentRenderProxy<
{ [key in PropNames]?: unknown },
RawBindings,
D,
C,
M
>
}
// overload 4: object format with object props declaration
// see `ExtractPropTypes` in ./componentProps.ts
export function createComponent<
PropsOptions,
RawBindings,
D,
C extends ComputedOptions = {},
M extends MethodOptions = {}
>(
options: ComponentOptionsWithProps<PropsOptions, RawBindings, D, C, M>
): {
// for Vetur and TSX support
new (): ComponentRenderProxy<
ExtractPropTypes<PropsOptions>,
RawBindings,
D,
C,
M,
ExtractPropTypes<PropsOptions, false>
>
}
// implementation, close to no-op
export function createComponent(options: any) {
return isFunction(options) ? { setup: options } : (options as any)
}
const emptyAppContext = createAppContext() const emptyAppContext = createAppContext()
export function createComponentInstance( export function createComponentInstance(

View File

@ -1,10 +1,10 @@
import { import {
ComponentInstance, ComponentInstance,
Data, Data,
ComponentOptions,
currentRenderingInstance, currentRenderingInstance,
currentInstance, currentInstance,
ComponentRenderProxy Component,
SetupContext
} from './component' } from './component'
import { import {
isFunction, isFunction,
@ -31,6 +31,71 @@ import {
} from './apiLifecycle' } from './apiLifecycle'
import { DebuggerEvent, reactive } from '@vue/reactivity' import { DebuggerEvent, reactive } from '@vue/reactivity'
import { warn } from './warning' import { warn } from './warning'
import { ComponentPropsOptions, ExtractPropTypes } from './componentProps'
import { Directive } from './directives'
import { VNodeChild } from './vnode'
import { ComponentRenderProxy } from './componentProxy'
interface ComponentOptionsBase<
Props,
RawBindings,
D,
C extends ComputedOptions,
M extends MethodOptions
> extends LegacyOptions<Props, RawBindings, D, C, M> {
setup?: (
this: null,
props: Props,
ctx: SetupContext
) => RawBindings | (() => VNodeChild) | void
name?: string
template?: string
// Note: we are intentionally using the signature-less `Function` type here
// since any type with signature will cause the whole inference to fail when
// the return expression contains reference to `this`.
// Luckily `render()` doesn't need any arguments nor does it care about return
// type.
render?: Function
components?: Record<string, Component>
directives?: Record<string, Directive>
}
export type ComponentOptionsWithoutProps<
Props = {},
RawBindings = {},
D = {},
C extends ComputedOptions = {},
M extends MethodOptions = {}
> = ComponentOptionsBase<Props, RawBindings, D, C, M> & {
props?: undefined
} & ThisType<ComponentRenderProxy<Props, RawBindings, D, C, M>>
export type ComponentOptionsWithArrayProps<
PropNames extends string = string,
RawBindings = {},
D = {},
C extends ComputedOptions = {},
M extends MethodOptions = {},
Props = { [key in PropNames]?: unknown }
> = ComponentOptionsBase<Props, RawBindings, D, C, M> & {
props: PropNames[]
} & ThisType<ComponentRenderProxy<Props, RawBindings, D, C, M>>
export type ComponentOptionsWithProps<
PropsOptions = ComponentPropsOptions,
RawBindings = {},
D = {},
C extends ComputedOptions = {},
M extends MethodOptions = {},
Props = ExtractPropTypes<PropsOptions>
> = ComponentOptionsBase<Props, RawBindings, D, C, M> & {
props: PropsOptions
} & ThisType<ComponentRenderProxy<Props, RawBindings, D, C, M>>
export type ComponentOptions =
| ComponentOptionsWithoutProps
| ComponentOptionsWithProps
| ComponentOptionsWithArrayProps
// TODO legacy component definition also supports constructors with .options // TODO legacy component definition also supports constructors with .options
type LegacyComponent = ComponentOptions type LegacyComponent = ComponentOptions
@ -60,6 +125,18 @@ type WatchHandler = (
onCleanup: CleanupRegistrator onCleanup: CleanupRegistrator
) => void ) => void
type ComponentWatchOptions = Record<
string,
string | WatchHandler | { handler: WatchHandler } & WatchOptions
>
type ComponentInjectOptions =
| string[]
| Record<
string | symbol,
string | symbol | { from: string | symbol; default?: any }
>
// TODO type inference for these options // TODO type inference for these options
export interface LegacyOptions< export interface LegacyOptions<
Props, Props,
@ -71,21 +148,16 @@ export interface LegacyOptions<
el?: any el?: any
// state // state
// Limitation: we cannot expose RawBindings on the `this` context for data
// since that leads to some sort of circular inference and breaks ThisType
// for the entire component.
data?: D | ((this: ComponentRenderProxy<Props>) => D) data?: D | ((this: ComponentRenderProxy<Props>) => D)
computed?: C computed?: C
methods?: M methods?: M
// TODO watch array // TODO watch array
watch?: Record< watch?: ComponentWatchOptions
string,
string | WatchHandler | { handler: WatchHandler } & WatchOptions
>
provide?: Data | Function provide?: Data | Function
inject?: inject?: ComponentInjectOptions
| string[]
| Record<
string | symbol,
string | symbol | { from: string | symbol; default?: any }
>
// composition // composition
mixins?: LegacyComponent[] mixins?: LegacyComponent[]

View File

@ -1,7 +1,33 @@
import { ComponentInstance } from './component' import { ComponentInstance, Data } from './component'
import { nextTick } from './scheduler' import { nextTick } from './scheduler'
import { instanceWatch } from './apiWatch' import { instanceWatch } from './apiWatch'
import { EMPTY_OBJ, hasOwn } from '@vue/shared' import { EMPTY_OBJ, hasOwn } from '@vue/shared'
import { ExtracComputedReturns } from './componentOptions'
import { UnwrapRef } from '@vue/reactivity'
// public properties exposed on the proxy, which is used as the render context
// in templates (as `this` in the render option)
export type ComponentRenderProxy<
P = {},
B = {},
D = {},
C = {},
M = {},
PublicProps = P
> = {
$data: D
$props: PublicProps
$attrs: Data
$refs: Data
$slots: Data
$root: ComponentInstance | null
$parent: ComponentInstance | null
$emit: (event: string, ...args: unknown[]) => void
} & P &
UnwrapRef<B> &
D &
ExtracComputedReturns<C> &
M
export const RenderProxyHandlers = { export const RenderProxyHandlers = {
get(target: ComponentInstance, key: string) { get(target: ComponentInstance, key: string) {

View File

@ -14,14 +14,11 @@ return applyDirectives(h(comp), [
import { VNode, cloneVNode } from './vnode' import { VNode, cloneVNode } from './vnode'
import { extend, isArray, isFunction } from '@vue/shared' import { extend, isArray, isFunction } from '@vue/shared'
import { warn } from './warning' import { warn } from './warning'
import { import { ComponentInstance, currentRenderingInstance } from './component'
ComponentInstance,
currentRenderingInstance,
ComponentRenderProxy
} from './component'
import { callWithAsyncErrorHandling, ErrorTypes } from './errorHandling' import { callWithAsyncErrorHandling, ErrorTypes } from './errorHandling'
import { HostNode } from './createRenderer' import { HostNode } from './createRenderer'
import { resolveAsset } from './apiOptions' import { resolveAsset } from './componentOptions'
import { ComponentRenderProxy } from './componentProxy'
export interface DirectiveBinding { export interface DirectiveBinding {
instance: ComponentRenderProxy | null instance: ComponentRenderProxy | null

View File

@ -9,13 +9,13 @@ import {
import { isObject, isArray } from '@vue/shared' import { isObject, isArray } from '@vue/shared'
import { Ref } from '@vue/reactivity' import { Ref } from '@vue/reactivity'
import { RawSlots } from './componentSlots' import { RawSlots } from './componentSlots'
import { FunctionalComponent } from './component'
import { import {
FunctionalComponent,
ComponentOptionsWithoutProps, ComponentOptionsWithoutProps,
ComponentOptionsWithArrayProps, ComponentOptionsWithArrayProps,
ComponentOptionsWithProps, ComponentOptionsWithProps,
ComponentOptions ComponentOptions
} from './component' } from './componentOptions'
import { ExtractPropTypes } from './componentProps' import { ExtractPropTypes } from './componentProps'
// `h` is a more user-friendly version of `createVNode` that allows omitting the // `h` is a more user-friendly version of `createVNode` that allows omitting the

View File

@ -1,6 +1,6 @@
// Public API ------------------------------------------------------------------ // Public API ------------------------------------------------------------------
export { createComponent } from './component' export { createComponent } from './apiCreateComponent'
export { nextTick } from './scheduler' export { nextTick } from './scheduler'
export * from './apiReactivity' export * from './apiReactivity'
export * from './apiWatch' export * from './apiWatch'