import { value } from '@vue/reactivity'
import { PropType } from '../src/componentProps'
+// mock React just for TSX testing purposes
+const React = {
+ createElement: () => {}
+}
+
test('createComponent type inference', () => {
- createComponent({
+ const MyComponent = createComponent({
props: {
a: Number,
// required should make property non-void
}
}
},
- render({ state, props }) {
- state.c * 2
- state.d.e.slice()
+ render(props) {
props.a && props.a * 2
props.b.slice()
props.bb.slice()
this.dd.push('dd')
}
})
- // rename this file to .tsx to test TSX props inference
- // ;(<MyComponent a={1} b="foo"/>)
+ // test TSX props inference
+ ;(<MyComponent a={1} b="foo" dd={['foo']}/>)
})
test('type inference w/ optional props declaration', () => {
- createComponent({
- setup(props) {
- props.anything
+ const Comp = createComponent({
+ setup(props: { msg: string }) {
+ props.msg
return {
a: 1
}
},
- render({ props, state }) {
- props.foobar
- state.a * 2
+ render(props) {
+ props.msg
this.a * 2
-
// should not make state and this indexable
// state.foobar
// this.foobar
}
})
+ ;(<Comp msg="hello"/>)
})
-// test('type inference w/ array props declaration', () => {
-// createComponent({
-// props: ['a', 'b'],
-// setup(props) {
-// props.a
-// props.b
-// return {
-// c: 1
-// }
-// },
-// render({ props, state }) {
-// props.a
-// props.b
-// state.c
-// this.a
-// this.b
-// this.c
-// }
-// })
-// })
+test('type inference w/ direct setup function', () => {
+ const Comp = createComponent((props: { msg: string }) => {
+ return () => <div>{props.msg}</div>
+ })
+ ;(<Comp msg="hello"/>)
+})
+
+test('type inference w/ array props declaration', () => {
+ const Comp = createComponent({
+ props: ['a', 'b'],
+ setup(props) {
+ props.a
+ props.b
+ return {
+ c: 1
+ }
+ },
+ render(props) {
+ props.a
+ props.b
+ this.a
+ this.b
+ this.c
+ }
+ })
+ ;(<Comp a={1} b={2}/>)
+})
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'
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
} & 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>
+}
+
+interface ComponentOptionsWithoutProps<Props = Data, RawBindings = Data> {
+ props?: undefined
+ setup?: (
+ this: ComponentRenderProxy<Props>,
+ props: Props
+ ) => RawBindings | RenderFunction<Props>
+ render?: RenderFunctionWithThis<Props, RawBindings>
}
-export interface FunctionalComponent<P = {}> {
- (ctx: ComponentInstance<P>): any
+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
}
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
} & 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(
next: null,
subTree: null as any,
update: null as any,
+ render: null,
renderProxy: null,
propsProxy: null,
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)
+ )
}
}