import { ReactiveEffect } from '@vue/observer'
import { isFunction, EMPTY_OBJ } from '@vue/shared'
import { RenderProxyHandlers } from './componentProxy'
-import {
- resolveProps,
- ComponentPropsOptions,
- initializeProps,
- PropValidator
-} from './componentProps'
+import { ComponentPropsOptions, PropValidator } from './componentProps'
interface Value<T> {
value: T
$slots: Data
}
-interface RenderFunctionArg<B = Data, P = Data> {
- state: B
- props: P
- attrs: Data
- slots: Slots
-}
-
export interface ComponentOptions<
RawProps = ComponentPropsOptions,
RawBindings = Data | void,
setup?: (props: Props) => RawBindings
render?: <B extends Bindings>(
this: ComponentPublicProperties<Props, B>,
- ctx: RenderFunctionArg<B, Props>
+ ctx: ComponentInstance<Props, B>
) => VNodeChild
}
export interface FunctionalComponent<P = {}> {
- (ctx: RenderFunctionArg): any
+ (ctx: ComponentInstance<P>): any
props?: ComponentPropsOptions<P>
displayName?: string
}
-export type Slot = (...args: any[]) => VNode[]
-
-export type Slots = Readonly<{
- [name: string]: Slot
-}>
-
type LifecycleHook = Function[] | null
export interface LifecycleHooks {
ec: LifecycleHook // errorCaptured
}
-export type ComponentInstance = {
+export type Slot = (...args: any[]) => VNode[]
+
+export type Slots = Readonly<{
+ [name: string]: Slot
+}>
+
+export type ComponentInstance<P = Data, S = Data> = {
type: FunctionalComponent | ComponentOptions
vnode: VNode
next: VNode | null
subTree: VNode
update: ReactiveEffect
-
// the rest are only for stateful components
- bindings: Data | null
- proxy: Data | null
-} & LifecycleHooks &
- ComponentPublicProperties
+ proxy: ComponentPublicProperties | null
+ state: S
+ props: P
+ attrs: Data
+ slots: Slots
+ refs: Data
+} & LifecycleHooks
// no-op, for type inference only
export function createComponent<
next: null,
subTree: null as any,
update: null as any,
- bindings: null,
proxy: null,
bm: null,
ec: null,
// public properties
- $attrs: EMPTY_OBJ,
- $props: EMPTY_OBJ,
- $refs: EMPTY_OBJ,
- $slots: EMPTY_OBJ,
- $state: EMPTY_OBJ
+ state: EMPTY_OBJ,
+ props: EMPTY_OBJ,
+ attrs: EMPTY_OBJ,
+ slots: EMPTY_OBJ,
+ refs: EMPTY_OBJ
}
}
export let currentInstance: ComponentInstance | null = null
-export function setupStatefulComponent(
- instance: ComponentInstance,
- props: Data | null
-) {
+export function setupStatefulComponent(instance: ComponentInstance) {
const Component = instance.type as ComponentOptions
// 1. create render proxy
- const proxy = (instance.proxy = new Proxy(instance, RenderProxyHandlers))
- // 2. resolve initial props
- initializeProps(instance, Component.props, props)
- // 3. call setup()
+ const proxy = (instance.proxy = new Proxy(
+ instance,
+ RenderProxyHandlers
+ ) as any)
+ // 2. call setup()
if (Component.setup) {
currentInstance = instance
- instance.bindings = Component.setup.call(proxy, proxy)
+ // TODO should pass reactive props here
+ instance.state = Component.setup.call(proxy, instance.props)
currentInstance = null
}
}
-export function renderComponentRoot(
- instance: ComponentInstance,
- useAlreadyResolvedProps?: boolean
-): VNode {
- const { type, vnode, proxy, bindings, $slots } = instance
- const renderArg: RenderFunctionArg = {
- state: bindings || EMPTY_OBJ,
- slots: $slots,
- props: null as any,
- attrs: null as any
- }
- if (useAlreadyResolvedProps) {
- // initial render for stateful components with setup()
- // props are already resolved
- renderArg.props = instance.$props
- renderArg.attrs = instance.$attrs
- } else {
- const { 0: props, 1: attrs } = resolveProps(
- (vnode as VNode).props,
- type.props
- )
- instance.$props = renderArg.props = props
- instance.$attrs = renderArg.attrs = attrs
- }
- if (isFunction(type)) {
- return normalizeVNode(type(renderArg))
+export function renderComponentRoot(instance: ComponentInstance): VNode {
+ const { type: Component, proxy } = instance
+ if (isFunction(Component)) {
+ return normalizeVNode(Component(instance))
} else {
- if (__DEV__ && !type.render) {
+ if (__DEV__ && !Component.render) {
// TODO warn missing render
}
- return normalizeVNode((type.render as Function).call(proxy, renderArg))
+ return normalizeVNode((Component.render as Function).call(proxy, instance))
}
}
const isReservedKey = (key: string): boolean => key[0] === '_' || key[0] === '$'
-export function initializeProps(
- instance: ComponentInstance,
- options: ComponentPropsOptions | undefined,
- rawProps: Data | null
-) {
- const { 0: props, 1: attrs } = resolveProps(rawProps, options)
- instance.$props = __DEV__ ? immutable(props) : props
- instance.$attrs = options
- ? __DEV__
- ? immutable(attrs)
- : attrs
- : instance.$props
-}
-
// resolve raw VNode data.
// - filter out reserved keys (key, ref, slots)
// - extract class and style into $attrs (to be merged onto child
// - if has declared props: put declared ones in `props`, the rest in `attrs`
// - else: everything goes in `props`.
-const EMPTY_PROPS = [EMPTY_OBJ, EMPTY_OBJ] as [Data, Data]
-
export function resolveProps(
+ instance: ComponentInstance,
rawProps: any,
_options: ComponentPropsOptions | void
-): [Data, Data] {
+) {
const hasDeclaredProps = _options != null
const options = normalizePropsOptions(_options) as NormalizedPropsOptions
if (!rawProps && !hasDeclaredProps) {
- return EMPTY_PROPS
+ return
}
const props: any = {}
let attrs: any = void 0
// if component has no declared props, $attrs === $props
attrs = props
}
- return [props, attrs]
+
+ instance.props = __DEV__ ? immutable(props) : props
+ instance.attrs = options
+ ? __DEV__
+ ? immutable(attrs)
+ : attrs
+ : instance.props
}
const normalizationMap = new WeakMap()
import { TEXT, CLASS, STYLE, PROPS, KEYED, UNKEYED } from './patchFlags'
import { queueJob, queuePostFlushCb, flushPostFlushCbs } from './scheduler'
import { effect, stop, ReactiveEffectOptions } from '@vue/observer'
+import { resolveProps } from './componentProps'
const prodEffectOptions = {
scheduler: queueJob
const instance: ComponentInstance = (vnode.component = createComponentInstance(
Component
))
- const needsSetup = typeof Component === 'object' && Component.setup
- if (needsSetup) {
- setupStatefulComponent(instance, vnode.props)
- }
instance.update = effect(() => {
if (!instance.vnode) {
// initial mount
instance.vnode = vnode
- const subTree = (instance.subTree = renderComponentRoot(
- instance,
- needsSetup
- ))
+ resolveProps(instance, vnode.props, Component.props)
+ // setup stateful
+ if (typeof Component === 'object' && Component.setup) {
+ setupStatefulComponent(instance)
+ }
+ const subTree = (instance.subTree = renderComponentRoot(instance))
// beforeMount hook
if (instance.bm !== null) {
invokeHooks(instance.bm)
next.component = instance
instance.vnode = next
instance.next = null
+ resolveProps(instance, next.props, Component.props)
}
const prevTree = instance.subTree
const nextTree = (instance.subTree = renderComponentRoot(instance))