vue3-core/packages/runtime-core/src/component.ts

239 lines
5.5 KiB
TypeScript
Raw Normal View History

2019-05-28 17:19:47 +08:00
import { VNode, normalizeVNode, VNodeChild } from './vnode'
import { ReactiveEffect } from '@vue/observer'
2019-05-28 19:36:15 +08:00
import { isFunction, EMPTY_OBJ } from '@vue/shared'
2019-05-29 10:43:27 +08:00
import { RenderProxyHandlers } from './componentProxy'
import {
resolveProps,
ComponentPropsOptions,
initializeProps,
PropValidator
} from './componentProps'
2019-05-28 13:27:31 +08:00
2019-05-28 17:19:47 +08:00
interface Value<T> {
value: T
}
2019-05-29 10:43:27 +08:00
export type Data = { [key: string]: any }
2019-05-28 17:19:47 +08:00
type UnwrapBindings<T> = {
[key in keyof T]: T[key] extends Value<infer V> ? V : T[key]
}
type ExtractPropTypes<PropOptions> = {
2019-05-29 10:43:27 +08:00
readonly [key in keyof PropOptions]: PropOptions[key] extends PropValidator<
infer V
>
2019-05-28 17:19:47 +08:00
? V
: PropOptions[key] extends null | undefined ? any : PropOptions[key]
}
2019-05-28 18:06:00 +08:00
export interface ComponentPublicProperties<P = Data, S = Data> {
2019-05-28 17:19:47 +08:00
$state: S
2019-05-28 18:06:00 +08:00
$props: P
$attrs: Data
// TODO
$refs: Data
$slots: Data
}
interface RenderFunctionArg<B = Data, P = Data> {
state: B
props: P
attrs: Data
slots: Slots
2019-05-28 17:19:47 +08:00
}
export interface ComponentOptions<
2019-05-28 18:06:00 +08:00
RawProps = ComponentPropsOptions,
RawBindings = Data | void,
2019-05-28 17:19:47 +08:00
Props = ExtractPropTypes<RawProps>,
Bindings = UnwrapBindings<RawBindings>
> {
props?: RawProps
setup?: (props: Props) => RawBindings
render?: <B extends Bindings>(
this: ComponentPublicProperties<Props, B>,
2019-05-28 18:06:00 +08:00
ctx: RenderFunctionArg<B, Props>
2019-05-28 17:19:47 +08:00
) => VNodeChild
}
2019-05-28 13:27:31 +08:00
2019-05-28 18:06:00 +08:00
export interface FunctionalComponent<P = {}> {
(ctx: RenderFunctionArg): any
props?: ComponentPropsOptions<P>
displayName?: string
}
export type Slot = (...args: any[]) => VNode[]
export type Slots = Readonly<{
[name: string]: Slot
}>
2019-05-28 19:36:15 +08:00
type LifecycleHook = Function[] | null
export interface LifecycleHooks {
bm: LifecycleHook // beforeMount
m: LifecycleHook // mounted
bu: LifecycleHook // beforeUpdate
u: LifecycleHook // updated
bum: LifecycleHook // beforeUnmount
um: LifecycleHook // unmounted
da: LifecycleHook // deactivated
a: LifecycleHook // activated
rtg: LifecycleHook // renderTriggered
rtc: LifecycleHook // renderTracked
ec: LifecycleHook // errorCaptured
}
export type ComponentInstance = {
2019-05-28 18:06:00 +08:00
type: FunctionalComponent | ComponentOptions
2019-05-29 10:43:27 +08:00
vnode: VNode
2019-05-28 17:19:47 +08:00
next: VNode | null
2019-05-29 10:43:27 +08:00
subTree: VNode
2019-05-28 17:19:47 +08:00
update: ReactiveEffect
2019-05-29 10:43:27 +08:00
// the rest are only for stateful components
2019-05-28 19:36:15 +08:00
bindings: Data | null
proxy: Data | null
} & LifecycleHooks &
ComponentPublicProperties
2019-05-29 10:43:27 +08:00
// no-op, for type inference only
export function createComponent<
RawProps,
RawBindings,
Props = ExtractPropTypes<RawProps>,
Bindings = UnwrapBindings<RawBindings>
>(
options: ComponentOptions<RawProps, RawBindings, Props, Bindings>
): {
// for TSX
new (): { $props: Props }
} {
return options as any
}
export function createComponentInstance(type: any): ComponentInstance {
return {
2019-05-28 19:36:15 +08:00
type,
2019-05-29 10:43:27 +08:00
vnode: null as any,
2019-05-28 19:36:15 +08:00
next: null,
2019-05-29 10:43:27 +08:00
subTree: null as any,
2019-05-28 19:36:15 +08:00
update: null as any,
bindings: null,
proxy: null,
bm: null,
m: null,
bu: null,
u: null,
um: null,
bum: null,
da: null,
a: null,
rtg: null,
rtc: null,
ec: null,
// public properties
$attrs: EMPTY_OBJ,
$props: EMPTY_OBJ,
$refs: EMPTY_OBJ,
$slots: EMPTY_OBJ,
$state: EMPTY_OBJ
}
}
export let currentInstance: ComponentInstance | null = null
2019-05-29 10:43:27 +08:00
export function setupStatefulComponent(
instance: ComponentInstance,
props: Data | null
) {
const Component = instance.type as ComponentOptions
2019-05-28 19:36:15 +08:00
// 1. create render proxy
const proxy = (instance.proxy = new Proxy(instance, RenderProxyHandlers))
// 2. resolve initial props
2019-05-29 10:43:27 +08:00
initializeProps(instance, Component.props, props)
2019-05-28 19:36:15 +08:00
// 3. call setup()
2019-05-29 10:43:27 +08:00
if (Component.setup) {
2019-05-28 19:36:15 +08:00
currentInstance = instance
2019-05-29 10:43:27 +08:00
instance.bindings = Component.setup.call(proxy, proxy)
2019-05-28 19:36:15 +08:00
currentInstance = null
}
}
2019-05-28 17:19:47 +08:00
2019-05-29 10:43:27 +08:00
export function renderComponentRoot(
instance: ComponentInstance,
useAlreadyResolvedProps?: boolean
): VNode {
2019-05-28 20:06:44 +08:00
const { type, vnode, proxy, bindings, $slots } = instance
2019-05-29 10:43:27 +08:00
const renderArg: RenderFunctionArg = {
2019-05-28 20:06:44 +08:00
state: bindings || EMPTY_OBJ,
2019-05-28 19:36:15 +08:00
slots: $slots,
2019-05-29 10:43:27 +08:00
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
2019-05-28 17:19:47 +08:00
}
if (isFunction(type)) {
return normalizeVNode(type(renderArg))
} else {
if (__DEV__ && !type.render) {
// TODO warn missing render
}
2019-05-28 19:36:15 +08:00
return normalizeVNode((type.render as Function).call(proxy, renderArg))
2019-05-28 17:19:47 +08:00
}
2019-05-28 13:27:31 +08:00
}
export function shouldUpdateComponent(
prevVNode: VNode,
nextVNode: VNode
): boolean {
const { props: prevProps } = prevVNode
const { props: nextProps } = nextVNode
// TODO handle slots
// If has different slots content, or has non-compiled slots,
// the child needs to be force updated.
// if (
// prevChildFlags !== nextChildFlags ||
// (nextChildFlags & ChildrenFlags.DYNAMIC_SLOTS) > 0
// ) {
// return true
// }
if (prevProps === nextProps) {
return false
}
if (prevProps === null) {
return nextProps !== null
}
if (nextProps === null) {
return prevProps !== null
}
const nextKeys = Object.keys(nextProps)
if (nextKeys.length !== Object.keys(prevProps).length) {
return true
}
for (let i = 0; i < nextKeys.length; i++) {
const key = nextKeys[i]
if (nextProps[key] !== prevProps[key]) {
return true
}
}
return false
}