Skip to content

Commit

Permalink
wip: defineComponent
Browse files Browse the repository at this point in the history
  • Loading branch information
yyx990803 committed Jun 1, 2022
1 parent 09beea9 commit 206f8a7
Show file tree
Hide file tree
Showing 16 changed files with 722 additions and 59 deletions.
3 changes: 2 additions & 1 deletion src/types/component.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand Down
12 changes: 2 additions & 10 deletions src/types/options.ts
Original file line number Diff line number Diff line change
@@ -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 = {
Expand All @@ -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
*/
Expand Down
10 changes: 9 additions & 1 deletion src/v3/apiSetup.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down
7 changes: 7 additions & 0 deletions src/v3/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 15 additions & 0 deletions types/common.d.ts
Original file line number Diff line number Diff line change
@@ -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 changes: 34 additions & 3 deletions types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ export {
ComponentOptions,
FunctionalComponentOptions,
RenderContext,
PropType,
PropOptions,
// PropType,
// PropOptions,
ComputedOptions,
WatchHandler,
WatchOptions,
Expand All @@ -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 changes: 3 additions & 35 deletions types/options.d.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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]>
}
Expand Down Expand Up @@ -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'
}
50 changes: 49 additions & 1 deletion types/test/v3/setup-test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Vue from '../../index'
import Vue, { defineComponent } from '../../index'

// object props
Vue.extend({
Expand Down Expand Up @@ -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')
}
}
})
2 changes: 1 addition & 1 deletion types/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@
"vue": ["../index.d.ts"]
}
},
"include": ["./*.d.ts", "*.ts"],
"include": ["."],
"compileOnSave": false
}
120 changes: 120 additions & 0 deletions types/v3-component-options.d.ts
Original file line number Diff line number Diff line change
@@ -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>
Loading

0 comments on commit 206f8a7

Please sign in to comment.