wip: support returning render fn from setup() + improve createComponent type inference

This commit is contained in:
Evan You
2019-06-12 15:43:19 +08:00
parent bfe6987323
commit fce6a8fa51
5 changed files with 176 additions and 71 deletions

View File

@@ -5,7 +5,7 @@ import {
state,
immutableState
} from '@vue/reactivity'
import { EMPTY_OBJ } from '@vue/shared'
import { EMPTY_OBJ, isFunction } from '@vue/shared'
import { RenderProxyHandlers } from './componentProxy'
import { ComponentPropsOptions, ExtractPropTypes } from './componentProps'
import { PROPS, DYNAMIC_SLOTS, FULL_PROPS } from './patchFlags'
@@ -14,9 +14,9 @@ import { STATEFUL_COMPONENT } from './typeFlags'
export type Data = { [key: string]: any }
export type ComponentPublicProperties<P = {}, S = {}> = {
export type ComponentRenderProxy<P = {}, S = {}, PublicProps = P> = {
$state: S
$props: P
$props: PublicProps
$attrs: Data
// TODO
@@ -28,22 +28,61 @@ export type ComponentPublicProperties<P = {}, S = {}> = {
} & P &
S
interface ComponentOptions<
RawProps = ComponentPropsOptions,
type RenderFunction<P = Data> = (
props: P,
slots: Slots,
attrs: Data,
vnode: VNode
) => any
type RenderFunctionWithThis<Props, RawBindings> = <
Bindings extends UnwrapValue<RawBindings>
>(
this: ComponentRenderProxy<Props, Bindings>,
props: Props,
slots: Slots,
attrs: Data,
vnode: VNode
) => VNodeChild
interface ComponentOptionsWithProps<
PropsOptions = ComponentPropsOptions,
RawBindings = Data,
Props = ExtractPropTypes<RawProps>,
ExposedProps = RawProps extends object ? Props : {}
Props = ExtractPropTypes<PropsOptions>
> {
props?: RawProps
setup?: (this: ComponentPublicProperties, props: Props) => RawBindings
render?: <State extends UnwrapValue<RawBindings>>(
this: ComponentPublicProperties<ExposedProps, State>,
ctx: ComponentInstance<Props, State>
) => VNodeChild
props: PropsOptions
setup?: (
this: ComponentRenderProxy<Props>,
props: Props
) => RawBindings | RenderFunction<Props>
render?: RenderFunctionWithThis<Props, RawBindings>
}
export interface FunctionalComponent<P = {}> {
(ctx: ComponentInstance<P>): any
interface ComponentOptionsWithoutProps<Props = Data, RawBindings = Data> {
props?: undefined
setup?: (
this: ComponentRenderProxy<Props>,
props: Props
) => RawBindings | RenderFunction<Props>
render?: RenderFunctionWithThis<Props, RawBindings>
}
interface ComponentOptionsWithArrayProps<
PropNames extends string,
RawBindings = Data,
Props = { [key in PropNames]?: any }
> {
props: PropNames[]
setup?: (
this: ComponentRenderProxy<Props>,
props: Props
) => RawBindings | RenderFunction<Props>
render?: RenderFunctionWithThis<Props, RawBindings>
}
type ComponentOptions = ComponentOptionsWithProps | ComponentOptionsWithoutProps
export interface FunctionalComponent<P = {}> extends RenderFunction<P> {
props?: ComponentPropsOptions<P>
displayName?: string
}
@@ -73,8 +112,9 @@ export type ComponentInstance<P = Data, S = Data> = {
subTree: VNode
update: ReactiveEffect
effects: ReactiveEffect[] | null
render: RenderFunction<P> | null
// the rest are only for stateful components
renderProxy: ComponentPublicProperties | null
renderProxy: ComponentRenderProxy | null
propsProxy: Data | null
state: S
props: P
@@ -84,13 +124,36 @@ export type ComponentInstance<P = Data, S = Data> = {
} & LifecycleHooks
// no-op, for type inference only
export function createComponent<RawProps, RawBindings>(
options: ComponentOptions<RawProps, RawBindings>
export function createComponent<Props>(
setup: (props: Props) => RenderFunction<Props>
): (props: Props) => any
export function createComponent<PropNames extends string, RawBindings>(
options: ComponentOptionsWithArrayProps<PropNames, RawBindings>
): {
// for TSX
new (): { $props: ExtractPropTypes<RawProps> }
} {
return options as any
// for Vetur and TSX support
new (): ComponentRenderProxy<
{ [key in PropNames]?: any },
UnwrapValue<RawBindings>
>
}
export function createComponent<Props, RawBindings>(
options: ComponentOptionsWithoutProps<Props, RawBindings>
): {
// for Vetur and TSX support
new (): ComponentRenderProxy<Props, UnwrapValue<RawBindings>>
}
export function createComponent<PropsOptions, RawBindings>(
options: ComponentOptionsWithProps<PropsOptions, RawBindings>
): {
// for Vetur and TSX support
new (): ComponentRenderProxy<
ExtractPropTypes<PropsOptions>,
UnwrapValue<RawBindings>,
ExtractPropTypes<PropsOptions, false>
>
}
export function createComponent(options: any) {
return isFunction(options) ? { setup: options } : (options as any)
}
export function createComponentInstance(
@@ -105,6 +168,7 @@ export function createComponentInstance(
next: null,
subTree: null as any,
update: null as any,
render: null,
renderProxy: null,
propsProxy: null,
@@ -153,23 +217,39 @@ export function setupStatefulComponent(instance: ComponentInstance) {
const propsProxy = (instance.propsProxy = setup.length
? immutableState(instance.props)
: null)
instance.state = state(setup.call(proxy, propsProxy))
const setupResult = setup.call(proxy, propsProxy)
if (isFunction(setupResult)) {
// setup returned a render function
instance.render = setupResult
} else {
// setup returned bindings
instance.state = state(setupResult)
if (__DEV__ && !Component.render) {
// TODO warn missing render fn
}
instance.render = Component.render as RenderFunction
}
currentInstance = null
}
}
export function renderComponentRoot(instance: ComponentInstance): VNode {
const { type: Component, vnode } = instance
const { type: Component, renderProxy, props, slots, attrs, vnode } = instance
if (vnode.shapeFlag & STATEFUL_COMPONENT) {
if (__DEV__ && !(Component as any).render) {
// TODO warn missing render
}
return normalizeVNode(
(Component as any).render.call(instance.renderProxy, instance)
(instance.render as RenderFunction).call(
renderProxy,
props,
slots,
attrs,
vnode
)
)
} else {
// functional
return normalizeVNode((Component as FunctionalComponent)(instance))
return normalizeVNode(
(Component as FunctionalComponent)(props, slots, attrs, vnode)
)
}
}

View File

@@ -31,11 +31,18 @@ export type PropType<T> = PropConstructor<T> | PropConstructor<T>[]
type PropConstructor<T> = { new (...args: any[]): T & object } | { (): T }
type RequiredKeys<T> = {
[K in keyof T]: T[K] extends { required: true } | { default: any } ? K : never
type RequiredKeys<T, MakeDefautRequired> = {
[K in keyof T]: T[K] extends
| { required: true }
| (MakeDefautRequired extends true ? { default: any } : never)
? K
: never
}[keyof T]
type OptionalKeys<T> = Exclude<keyof T, RequiredKeys<T>>
type OptionalKeys<T, MakeDefautRequired> = Exclude<
keyof T,
RequiredKeys<T, MakeDefautRequired>
>
type InferPropType<T> = T extends null
? any // null & true would fail to infer
@@ -45,9 +52,18 @@ type InferPropType<T> = T extends null
? { [key: string]: any }
: T extends Prop<infer V> ? V : T
export type ExtractPropTypes<O> = O extends object
? { readonly [K in RequiredKeys<O>]: InferPropType<O[K]> } &
{ readonly [K in OptionalKeys<O>]?: InferPropType<O[K]> }
export type ExtractPropTypes<
O,
MakeDefautRequired extends boolean = true
> = O extends object
? {
readonly [K in RequiredKeys<O, MakeDefautRequired>]: InferPropType<O[K]>
} &
{
readonly [K in OptionalKeys<O, MakeDefautRequired>]?: InferPropType<
O[K]
>
}
: { [K in string]: any }
const enum BooleanFlags {