wip: defineComponent

This commit is contained in:
Evan You 2022-06-01 21:55:33 +08:00
parent 09beea9ae7
commit 206f8a7f09
16 changed files with 722 additions and 59 deletions

View File

@ -1,6 +1,7 @@
import type VNode from 'core/vdom/vnode'
import type Watcher from 'core/observer/watcher'
import { ComponentOptions, SetupContext } from './options'
import { ComponentOptions } from './options'
import { SetupContext } from 'v3/apiSetup'
import { ScopedSlotsData, VNodeChildren, VNodeData } from './vnode'
import { GlobalAPI } from './global-api'
import { EffectScope } from 'v3/reactivity/effectScope'

View File

@ -1,5 +1,6 @@
import VNode from 'core/vdom/vnode'
import { DebuggerEvent } from 'v3'
import { DebuggerEvent } from 'v3/debug'
import { SetupContext } from 'v3/apiSetup'
import { Component } from './component'
export type InternalComponentOptions = {
@ -12,15 +13,6 @@ export type InternalComponentOptions = {
type InjectKey = string | Symbol
/**
* @internal
*/
export interface SetupContext {
attrs: Record<string, any>
slots: Record<string, () => VNode[]>
emit: (event: string, ...args: any[]) => any
}
/**
* @internal
*/

View File

@ -1,11 +1,19 @@
import { Component } from 'types/component'
import type { SetupContext } from 'types/options'
import { def, invokeWithErrorHandling, isReserved, warn } from '../core/util'
import VNode from '../core/vdom/vnode'
import { bind, emptyObject, isFunction, isObject } from '../shared/util'
import { currentInstance, setCurrentInstance } from './currentInstance'
import { isRef } from './reactivity/ref'
/**
* @internal
*/
export interface SetupContext {
attrs: Record<string, any>
slots: Record<string, () => VNode[]>
emit: (event: string, ...args: any[]) => any
}
export function initSetup(vm: Component) {
const options = vm.$options
const setup = options.setup

View File

@ -74,4 +74,11 @@ export { useSlots, useAttrs } from './apiSetup'
export { nextTick } from 'core/util/next-tick'
export { set, del } from 'core/observer'
/**
* @internal type is manually declared in <root>/types/v3-define-component.d.ts
*/
export function defineComponent(options: any) {
return options
}
export * from './apiLifecycle'

15
types/common.d.ts vendored Normal file
View File

@ -0,0 +1,15 @@
export type Data = { [key: string]: unknown }
export type UnionToIntersection<U> = (
U extends any ? (k: U) => void : never
) extends (k: infer I) => void
? I
: never
// Conditional returns can enforce identical types.
// See here: https://github.com/Microsoft/TypeScript/issues/27024#issuecomment-421529650
// prettier-ignore
type Equal<Left, Right> =
(<U>() => U extends Left ? 1 : 0) extends (<U>() => U extends Right ? 1 : 0) ? true : false;
export type HasDefined<T> = Equal<T, unknown> extends true ? false : true

37
types/index.d.ts vendored
View File

@ -11,8 +11,8 @@ export {
ComponentOptions,
FunctionalComponentOptions,
RenderContext,
PropType,
PropOptions,
// PropType,
// PropOptions,
ComputedOptions,
WatchHandler,
WatchOptions,
@ -32,5 +32,36 @@ export {
VNodeDirective
} from './vnode'
export * from './v3'
export * from './v3-manual-apis'
export * from './v3-generated'
export { Data } from './common'
export { SetupContext } from './v3-setup-context'
export { defineComponent } from './v3-define-component'
// export { defineAsyncComponent } from './defineAsyncComponent'
export {
SetupFunction,
// v2 already has option with same name and it's for a single computed
ComputedOptions as ComponentComputedOptions,
MethodOptions as ComponentMethodOptions,
ComponentPropsOptions
} from './v3-component-options'
export {
ComponentInstance,
ComponentPublicInstance,
ComponentRenderProxy
} from './v3-component-proxy'
export {
PropType,
PropOptions,
ExtractPropTypes,
ExtractDefaultPropTypes
} from './v3-component-props'
export {
DirectiveModifiers,
DirectiveBinding,
DirectiveHook,
ObjectDirective,
FunctionDirective,
Directive
} from './v3-directive'

38
types/options.d.ts vendored
View File

@ -1,6 +1,7 @@
import { Vue, CreateElement, CombinedVueInstance } from './vue'
import { VNode, VNodeData, VNodeDirective, NormalizedScopedSlot } from './vnode'
import { SetupContext } from './v3'
import { SetupContext } from './v3-setup-context'
import { DebuggerEvent } from './v3-generated'
type Constructor = {
new (...args: any[]): any
@ -249,22 +250,10 @@ export interface RenderContext<Props = DefaultProps> {
injections: any
}
export type Prop<T> =
| { (): T }
| { new (...args: never[]): T & object }
| { new (...args: string[]): Function }
export type PropType<T> = Prop<T> | Prop<T>[]
import { PropOptions, PropType } from './v3-component-props'
export type PropValidator<T> = PropOptions<T> | PropType<T>
export interface PropOptions<T = any> {
type?: PropType<T>
required?: boolean
default?: T | null | undefined | (() => T | null | undefined)
validator?(value: T): boolean
}
export type RecordPropsDefinition<T> = {
[K in keyof T]: PropValidator<T[K]>
}
@ -316,24 +305,3 @@ export type InjectOptions =
[key: string]: InjectKey | { from?: InjectKey; default?: any }
}
| string[]
export type DebuggerEvent = {
target: object
type: TrackOpTypes | TriggerOpTypes
key?: any
newValue?: any
oldValue?: any
oldTarget?: Map<any, any> | Set<any>
}
export const enum TrackOpTypes {
GET = 'get',
TOUCH = 'touch'
}
export const enum TriggerOpTypes {
SET = 'set',
ADD = 'add',
DELETE = 'delete',
ARRAY_MUTATION = 'array mutation'
}

View File

@ -1,4 +1,4 @@
import Vue from '../../index'
import Vue, { defineComponent } from '../../index'
// object props
Vue.extend({
@ -30,3 +30,51 @@ Vue.extend({
ctx.slots.default && ctx.slots.default()
}
})
// object props
defineComponent({
props: {
foo: String,
bar: Number
},
setup(props) {
// @ts-expect-error
props.foo.slice(1, 2)
props.foo?.slice(1, 2)
// @ts-expect-error
props.bar + 123
props.bar?.toFixed(2)
}
})
// array props
defineComponent({
props: ['foo', 'bar'],
setup(props) {
props.foo
props.bar
}
})
// context
defineComponent({
emits: ['foo'],
setup(_props, ctx) {
if (ctx.attrs.id) {
}
ctx.emit('foo')
// @ts-expect-error
ctx.emit('ok')
ctx.slots.default && ctx.slots.default()
},
methods: {
foo() {
this.$emit('foo')
// @ts-expect-error
this.$emit('bar')
}
}
})

View File

@ -12,6 +12,6 @@
"vue": ["../index.d.ts"]
}
},
"include": ["./*.d.ts", "*.ts"],
"include": ["."],
"compileOnSave": false
}

120
types/v3-component-options.d.ts vendored Normal file
View File

@ -0,0 +1,120 @@
import { Vue } from './vue'
import { VNode } from './vnode'
import { ComponentOptions as Vue2ComponentOptions } from './options'
import { EmitsOptions, SetupContext } from './v3-setup-context'
import { Data } from './common'
import { ComponentPropsOptions, ExtractPropTypes } from './v3-component-props'
import { ComponentRenderProxy } from './v3-component-proxy'
export { ComponentPropsOptions } from './v3-component-props'
export type ComputedGetter<T> = (ctx?: any) => T
export type ComputedSetter<T> = (v: T) => void
export interface WritableComputedOptions<T> {
get: ComputedGetter<T>
set: ComputedSetter<T>
}
export type ComputedOptions = Record<
string,
ComputedGetter<any> | WritableComputedOptions<any>
>
export interface MethodOptions {
[key: string]: Function
}
export type SetupFunction<
Props,
RawBindings = {},
Emits extends EmitsOptions = {}
> = (
this: void,
props: Readonly<Props>,
ctx: SetupContext<Emits>
) => RawBindings | (() => VNode | null) | void
interface ComponentOptionsBase<
Props,
D = Data,
C extends ComputedOptions = {},
M extends MethodOptions = {}
> extends Omit<
Vue2ComponentOptions<Vue, D, M, C, Props>,
'data' | 'computed' | 'method' | 'setup' | 'props'
> {
// allow any custom options
[key: string]: any
// rewrite options api types
data?: (this: Props & Vue, vm: Props) => D
computed?: C
methods?: M
}
export type ExtractComputedReturns<T extends any> = {
[key in keyof T]: T[key] extends { get: (...args: any[]) => infer TReturn }
? TReturn
: T[key] extends (...args: any[]) => infer TReturn
? TReturn
: never
}
export type ComponentOptionsWithProps<
PropsOptions = ComponentPropsOptions,
RawBindings = Data,
D = Data,
C extends ComputedOptions = {},
M extends MethodOptions = {},
Mixin = {},
Extends = {},
Emits extends EmitsOptions = {},
EmitsNames extends string = string,
Props = ExtractPropTypes<PropsOptions>
> = ComponentOptionsBase<Props, D, C, M> & {
props?: PropsOptions
emits?: (Emits | EmitsNames[]) & ThisType<void>
setup?: SetupFunction<Props, RawBindings, Emits>
} & ThisType<
ComponentRenderProxy<Props, RawBindings, D, C, M, Mixin, Extends, Emits>
>
export type ComponentOptionsWithArrayProps<
PropNames extends string = string,
RawBindings = Data,
D = Data,
C extends ComputedOptions = {},
M extends MethodOptions = {},
Mixin = {},
Extends = {},
Emits extends EmitsOptions = {},
EmitsNames extends string = string,
Props = Readonly<{ [key in PropNames]?: any }>
> = ComponentOptionsBase<Props, D, C, M> & {
props?: PropNames[]
emits?: (Emits | EmitsNames[]) & ThisType<void>
setup?: SetupFunction<Props, RawBindings, Emits>
} & ThisType<
ComponentRenderProxy<Props, RawBindings, D, C, M, Mixin, Extends, Emits>
>
export type ComponentOptionsWithoutProps<
Props = {},
RawBindings = Data,
D = Data,
C extends ComputedOptions = {},
M extends MethodOptions = {},
Mixin = {},
Extends = {},
Emits extends EmitsOptions = {},
EmitsNames extends string = string
> = ComponentOptionsBase<Props, D, C, M> & {
props?: undefined
emits?: (Emits | EmitsNames[]) & ThisType<void>
setup?: SetupFunction<Props, RawBindings, Emits>
} & ThisType<
ComponentRenderProxy<Props, RawBindings, D, C, M, Mixin, Extends, Emits>
>
export type WithLegacyAPI<T, D, C, M, Props> = T &
Omit<Vue2ComponentOptions<Vue, D, M, C, Props>, keyof T>

100
types/v3-component-props.d.ts vendored Normal file
View File

@ -0,0 +1,100 @@
import { Data } from './common'
export type ComponentPropsOptions<P = Data> =
| ComponentObjectPropsOptions<P>
| string[]
export type ComponentObjectPropsOptions<P = Data> = {
[K in keyof P]: Prop<P[K]> | null
}
export type Prop<T, D = T> = PropOptions<T, D> | PropType<T>
type DefaultFactory<T> = () => T | null | undefined
export interface PropOptions<T = any, D = T> {
type?: PropType<T> | true | null
required?: boolean
default?: D | DefaultFactory<D> | null | undefined | object
validator?(value: unknown): boolean
}
export type PropType<T> = PropConstructor<T> | PropConstructor<T>[]
type PropConstructor<T> =
| { new (...args: any[]): T & object }
| { (): T }
| { new (...args: string[]): Function }
type RequiredKeys<T> = {
[K in keyof T]: T[K] extends
| { required: true }
| { default: any }
| BooleanConstructor
| { type: BooleanConstructor }
? K
: never
}[keyof T]
type OptionalKeys<T> = Exclude<keyof T, RequiredKeys<T>>
type ExtractFunctionPropType<
T extends Function,
TArgs extends Array<any> = any[],
TResult = any
> = T extends (...args: TArgs) => TResult ? T : never
type ExtractCorrectPropType<T> = T extends Function
? ExtractFunctionPropType<T>
: Exclude<T, Function>
// prettier-ignore
type InferPropType<T> = T extends null
? any // null & true would fail to infer
: T extends { type: null | true }
? any // As TS issue https://github.com/Microsoft/TypeScript/issues/14829 // somehow `ObjectConstructor` when inferred from { (): T } becomes `any` // `BooleanConstructor` when inferred from PropConstructor(with PropMethod) becomes `Boolean`
: T extends ObjectConstructor | { type: ObjectConstructor }
? Record<string, any>
: T extends BooleanConstructor | { type: BooleanConstructor }
? boolean
: T extends DateConstructor | { type: DateConstructor}
? Date
: T extends FunctionConstructor
? Function
: T extends Prop<infer V, infer D>
? unknown extends V
? D extends null | undefined
? V
: D
: ExtractCorrectPropType<V>
: T
export type ExtractPropTypes<O> = {
// use `keyof Pick<O, RequiredKeys<O>>` instead of `RequiredKeys<O>` to support IDE features
[K in keyof Pick<O, RequiredKeys<O>>]: InferPropType<O[K]>
} & {
// use `keyof Pick<O, OptionalKeys<O>>` instead of `OptionalKeys<O>` to support IDE features
[K in keyof Pick<O, OptionalKeys<O>>]?: InferPropType<O[K]>
}
type DefaultKeys<T> = {
[K in keyof T]: T[K] extends
| {
default: any
}
| BooleanConstructor
| { type: BooleanConstructor }
? T[K] extends {
type: BooleanConstructor
required: true
}
? never
: K
: never
}[keyof T]
// extract props which defined with default from prop options
export type ExtractDefaultPropTypes<O> = O extends object
? // use `keyof Pick<O, DefaultKeys<O>>` instead of `DefaultKeys<O>` to support IDE features
{ [K in keyof Pick<O, DefaultKeys<O>>]: InferPropType<O[K]> }
: {}

189
types/v3-component-proxy.d.ts vendored Normal file
View File

@ -0,0 +1,189 @@
import { ExtractDefaultPropTypes, ExtractPropTypes } from './v3-component-props'
import {
nextTick,
ShallowUnwrapRef,
UnwrapNestedRefs,
WatchOptions,
WatchStopHandle
} from './v3-generated'
import { Data } from './common'
import { Vue, VueConstructor } from './vue'
import { ComponentOptions as Vue2ComponentOptions } from './options'
import {
ComputedOptions,
MethodOptions,
ExtractComputedReturns
} from './v3-component-options'
import {
ComponentRenderEmitFn,
EmitFn,
EmitsOptions,
ObjectEmitsOptions,
Slots
} from './v3-setup-context'
type EmitsToProps<T extends EmitsOptions> = T extends string[]
? {
[K in string & `on${Capitalize<T[number]>}`]?: (...args: any[]) => any
}
: T extends ObjectEmitsOptions
? {
[K in string &
`on${Capitalize<string & keyof T>}`]?: K extends `on${infer C}`
? T[Uncapitalize<C>] extends null
? (...args: any[]) => any
: (
...args: T[Uncapitalize<C>] extends (...args: infer P) => any
? P
: never
) => any
: never
}
: {}
export type ComponentInstance = InstanceType<VueConstructor>
// 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 = {}, // props type extracted from props option
B = {}, // raw bindings returned from setup()
D = {}, // return from data()
C extends ComputedOptions = {},
M extends MethodOptions = {},
Mixin = {},
Extends = {},
Emits extends EmitsOptions = {},
PublicProps = P,
Defaults = {},
MakeDefaultsOptional extends boolean = false
> = {
$data: D
$props: Readonly<
MakeDefaultsOptional extends true
? Partial<Defaults> & Omit<P & PublicProps, keyof Defaults>
: P & PublicProps
>
$attrs: Record<string, string>
$emit: ComponentRenderEmitFn<
Emits,
keyof Emits,
ComponentRenderProxy<
P,
B,
D,
C,
M,
Mixin,
Extends,
Emits,
PublicProps,
Defaults,
MakeDefaultsOptional
>
>
} & Readonly<P> &
ShallowUnwrapRef<B> &
D &
M &
ExtractComputedReturns<C> &
Omit<Vue, '$data' | '$props' | '$attrs' | '$emit'>
// for Vetur and TSX support
type VueConstructorProxy<
PropsOptions,
RawBindings,
Data,
Computed extends ComputedOptions,
Methods extends MethodOptions,
Mixin = {},
Extends = {},
Emits extends EmitsOptions = {},
Props = ExtractPropTypes<PropsOptions> &
({} extends Emits ? {} : EmitsToProps<Emits>)
> = Omit<VueConstructor, never> & {
new (...args: any[]): ComponentRenderProxy<
Props,
ShallowUnwrapRef<RawBindings>,
Data,
Computed,
Methods,
Mixin,
Extends,
Emits,
Props,
ExtractDefaultPropTypes<PropsOptions>,
true
>
}
type DefaultData<V> = object | ((this: V) => object)
type DefaultMethods<V> = { [key: string]: (this: V, ...args: any[]) => any }
type DefaultComputed = { [key: string]: any }
export type VueProxy<
PropsOptions,
RawBindings,
Data = DefaultData<Vue>,
Computed extends ComputedOptions = DefaultComputed,
Methods extends MethodOptions = DefaultMethods<Vue>,
Mixin = {},
Extends = {},
Emits extends EmitsOptions = {}
> = Vue2ComponentOptions<
Vue,
ShallowUnwrapRef<RawBindings> & Data,
Methods,
Computed,
PropsOptions,
ExtractPropTypes<PropsOptions>
> &
VueConstructorProxy<
PropsOptions,
RawBindings,
Data,
Computed,
Methods,
Mixin,
Extends,
Emits
>
// public properties exposed on the proxy, which is used as the render context
// in templates (as `this` in the render option)
export type ComponentPublicInstance<
P = {}, // props type extracted from props option
B = {}, // raw bindings returned from setup()
D = {}, // return from data()
C extends ComputedOptions = {},
M extends MethodOptions = {},
E extends EmitsOptions = {},
PublicProps = P,
Defaults = {},
MakeDefaultsOptional extends boolean = false
> = {
$data: D
$props: MakeDefaultsOptional extends true
? Partial<Defaults> & Omit<P & PublicProps, keyof Defaults>
: P & PublicProps
$attrs: Data
$refs: Data
$slots: Slots
$root: ComponentPublicInstance | null
$parent: ComponentPublicInstance | null
$emit: EmitFn<E>
$el: any
// $options: Options & MergedComponentOptionsOverride
$forceUpdate: () => void
$nextTick: typeof nextTick
$watch(
source: string | Function,
cb: Function,
options?: WatchOptions
): WatchStopHandle
} & P &
ShallowUnwrapRef<B> &
UnwrapNestedRefs<D> &
ExtractComputedReturns<C> &
M

119
types/v3-define-component.d.ts vendored Normal file
View File

@ -0,0 +1,119 @@
import { ComponentPropsOptions } from './v3-component-props'
import {
MethodOptions,
ComputedOptions,
ComponentOptionsWithoutProps,
ComponentOptionsWithArrayProps,
ComponentOptionsWithProps
} from './v3-component-options'
import { VueProxy } from './v3-component-proxy'
import { Data, HasDefined } from './common'
import { EmitsOptions } from './v3-setup-context'
/**
* overload 1: object format with no props
*/
export function defineComponent<
RawBindings,
D = Data,
C extends ComputedOptions = {},
M extends MethodOptions = {},
Mixin = {},
Extends = {},
Emits extends EmitsOptions = {},
EmitsNames extends string = string
>(
options: ComponentOptionsWithoutProps<
{},
RawBindings,
D,
C,
M,
Mixin,
Extends,
Emits,
EmitsNames
>
): VueProxy<{}, RawBindings, D, C, M, Mixin, Extends, Emits>
/**
* overload 2: object format with array props declaration
* props inferred as `{ [key in PropNames]?: any }`
*
* return type is for Vetur and TSX support
*/
export function defineComponent<
PropNames extends string,
RawBindings = Data,
D = Data,
C extends ComputedOptions = {},
M extends MethodOptions = {},
Mixin = {},
Extends = {},
Emits extends EmitsOptions = {},
EmitsNames extends string = string,
PropsOptions extends ComponentPropsOptions = ComponentPropsOptions
>(
options: ComponentOptionsWithArrayProps<
PropNames,
RawBindings,
D,
C,
M,
Mixin,
Extends,
Emits,
EmitsNames
>
): VueProxy<
Readonly<{ [key in PropNames]?: any }>,
RawBindings,
D,
C,
M,
Mixin,
Extends,
Emits
>
/**
* overload 3: object format with object props declaration
*
* see `ExtractPropTypes` in './componentProps.ts'
*/
export function defineComponent<
Props,
RawBindings = Data,
D = Data,
C extends ComputedOptions = {},
M extends MethodOptions = {},
Mixin = {},
Extends = {},
Emits extends EmitsOptions = {},
EmitsNames extends string = string,
PropsOptions extends ComponentPropsOptions = ComponentPropsOptions
>(
options: HasDefined<Props> extends true
? ComponentOptionsWithProps<
PropsOptions,
RawBindings,
D,
C,
M,
Mixin,
Extends,
Emits,
EmitsNames,
Props
>
: ComponentOptionsWithProps<
PropsOptions,
RawBindings,
D,
C,
M,
Mixin,
Extends,
Emits,
EmitsNames
>
): VueProxy<PropsOptions, RawBindings, D, C, M, Mixin, Extends, Emits>

29
types/v3-directive.ts Normal file
View File

@ -0,0 +1,29 @@
import type { VNodeDirective, VNode } from './vnode'
export type DirectiveModifiers = Record<string, boolean>
export interface DirectiveBinding<V> extends Readonly<VNodeDirective> {
readonly modifiers: DirectiveModifiers
readonly value: V
readonly oldValue: V | null
}
export type DirectiveHook<T = any, Prev = VNode | null, V = any> = (
el: T,
binding: DirectiveBinding<V>,
vnode: VNode,
prevVNode: Prev
) => void
export interface ObjectDirective<T = any, V = any> {
bind?: DirectiveHook<T, any, V>
inserted?: DirectiveHook<T, any, V>
update?: DirectiveHook<T, any, V>
componentUpdated?: DirectiveHook<T, any, V>
unbind?: DirectiveHook<T, any, V>
}
export type FunctionDirective<T = any, V = any> = DirectiveHook<T, any, V>
export type Directive<T = any, V = any> =
| ObjectDirective<T, V>
| FunctionDirective<T, V>

View File

@ -1,12 +1,6 @@
import { VNode } from './vnode'
import { SetupContext } from './v3-setup-context'
import { CreateElement, Vue } from './vue'
export interface SetupContext {
attrs: Record<string, any>
slots: Record<string, (() => VNode[]) | undefined>
emit: (event: string, ...args: any[]) => any
}
export function getCurrentInstance(): { proxy: Vue } | null
export const h: CreateElement

42
types/v3-setup-context.d.ts vendored Normal file
View File

@ -0,0 +1,42 @@
import { VNode } from './vnode'
import { Data, UnionToIntersection } from './common'
import { Vue } from './vue'
export type Slot = (...args: any[]) => VNode[]
export type Slots = Record<string, Slot | undefined>
export type ObjectEmitsOptions = Record<
string,
((...args: any[]) => any) | null
>
export type EmitsOptions = ObjectEmitsOptions | string[]
export type EmitFn<
Options = ObjectEmitsOptions,
Event extends keyof Options = keyof Options,
ReturnType extends void | Vue = void
> = Options extends Array<infer V>
? (event: V, ...args: any[]) => ReturnType
: {} extends Options // if the emit is empty object (usually the default value for emit) should be converted to function
? (event: string, ...args: any[]) => ReturnType
: UnionToIntersection<
{
[key in Event]: Options[key] extends (...args: infer Args) => any
? (event: key, ...args: Args) => ReturnType
: (event: key, ...args: any[]) => ReturnType
}[Event]
>
export type ComponentRenderEmitFn<
Options = ObjectEmitsOptions,
Event extends keyof Options = keyof Options,
T extends Vue | void = void
> = EmitFn<Options, Event, T>
export interface SetupContext<E extends EmitsOptions = {}> {
attrs: Data
slots: Slots
emit: EmitFn<E>
}