2019-05-28 17:19:47 +08:00
|
|
|
import { VNode, normalizeVNode, VNodeChild } from './vnode'
|
2019-05-30 23:16:15 +08:00
|
|
|
import {
|
|
|
|
ReactiveEffect,
|
|
|
|
UnwrapValue,
|
|
|
|
observable,
|
|
|
|
immutable
|
|
|
|
} 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'
|
2019-05-31 18:07:43 +08:00
|
|
|
import { ComponentPropsOptions, ExtractPropTypes } from './componentProps'
|
2019-05-31 12:25:11 +08:00
|
|
|
import { PROPS, DYNAMIC_SLOTS, FULL_PROPS } from './patchFlags'
|
2019-05-31 18:07:43 +08:00
|
|
|
import { Slots } from './componentSlots'
|
2019-05-28 13:27:31 +08:00
|
|
|
|
2019-05-29 10:43:27 +08:00
|
|
|
export type Data = { [key: string]: any }
|
|
|
|
|
2019-05-30 13:35:50 +08:00
|
|
|
export type 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
|
2019-05-29 13:43:46 +08:00
|
|
|
|
|
|
|
$root: ComponentInstance | null
|
|
|
|
$parent: ComponentInstance | null
|
2019-05-30 13:35:50 +08:00
|
|
|
} & P &
|
|
|
|
S
|
2019-05-28 18:06:00 +08:00
|
|
|
|
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>,
|
2019-05-30 13:35:50 +08:00
|
|
|
Bindings = UnwrapValue<RawBindings>
|
2019-05-28 17:19:47 +08:00
|
|
|
> {
|
|
|
|
props?: RawProps
|
|
|
|
setup?: (props: Props) => RawBindings
|
2019-05-30 13:35:50 +08:00
|
|
|
render?: <State extends Bindings>(
|
|
|
|
this: ComponentPublicProperties<Props, State>,
|
|
|
|
ctx: ComponentInstance<Props, State>
|
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 = {}> {
|
2019-05-29 11:36:16 +08:00
|
|
|
(ctx: ComponentInstance<P>): any
|
2019-05-28 18:06:00 +08:00
|
|
|
props?: ComponentPropsOptions<P>
|
|
|
|
displayName?: string
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2019-05-29 11:36:16 +08:00
|
|
|
export type ComponentInstance<P = Data, S = Data> = {
|
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 23:44:59 +08:00
|
|
|
effects: ReactiveEffect[] | null
|
2019-05-29 10:43:27 +08:00
|
|
|
// the rest are only for stateful components
|
2019-05-30 23:16:15 +08:00
|
|
|
renderProxy: ComponentPublicProperties | null
|
|
|
|
propsProxy: Data | null
|
2019-05-29 11:36:16 +08:00
|
|
|
state: S
|
|
|
|
props: P
|
|
|
|
attrs: Data
|
|
|
|
slots: Slots
|
|
|
|
refs: Data
|
|
|
|
} & LifecycleHooks
|
2019-05-28 19:36:15 +08:00
|
|
|
|
2019-05-29 10:43:27 +08:00
|
|
|
// no-op, for type inference only
|
|
|
|
export function createComponent<
|
|
|
|
RawProps,
|
|
|
|
RawBindings,
|
|
|
|
Props = ExtractPropTypes<RawProps>,
|
2019-05-30 13:35:50 +08:00
|
|
|
Bindings = UnwrapValue<RawBindings>
|
2019-05-29 10:43:27 +08:00
|
|
|
>(
|
|
|
|
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,
|
2019-05-30 23:16:15 +08:00
|
|
|
renderProxy: null,
|
|
|
|
propsProxy: null,
|
2019-05-28 19:36:15 +08:00
|
|
|
|
|
|
|
bm: null,
|
|
|
|
m: null,
|
|
|
|
bu: null,
|
|
|
|
u: null,
|
|
|
|
um: null,
|
|
|
|
bum: null,
|
|
|
|
da: null,
|
|
|
|
a: null,
|
|
|
|
rtg: null,
|
|
|
|
rtc: null,
|
|
|
|
ec: null,
|
2019-05-29 23:44:59 +08:00
|
|
|
effects: null,
|
2019-05-28 19:36:15 +08:00
|
|
|
|
|
|
|
// public properties
|
2019-05-29 11:36:16 +08:00
|
|
|
state: EMPTY_OBJ,
|
|
|
|
props: EMPTY_OBJ,
|
|
|
|
attrs: EMPTY_OBJ,
|
|
|
|
slots: EMPTY_OBJ,
|
|
|
|
refs: EMPTY_OBJ
|
2019-05-28 19:36:15 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export let currentInstance: ComponentInstance | null = null
|
|
|
|
|
2019-05-29 11:36:16 +08:00
|
|
|
export function setupStatefulComponent(instance: ComponentInstance) {
|
2019-05-29 10:43:27 +08:00
|
|
|
const Component = instance.type as ComponentOptions
|
2019-05-28 19:36:15 +08:00
|
|
|
// 1. create render proxy
|
2019-05-30 23:16:15 +08:00
|
|
|
const proxy = (instance.renderProxy = new Proxy(
|
2019-05-29 11:36:16 +08:00
|
|
|
instance,
|
|
|
|
RenderProxyHandlers
|
|
|
|
) as any)
|
|
|
|
// 2. call setup()
|
2019-05-30 23:16:15 +08:00
|
|
|
const { setup } = Component
|
|
|
|
if (setup) {
|
2019-05-28 19:36:15 +08:00
|
|
|
currentInstance = instance
|
2019-05-30 23:16:15 +08:00
|
|
|
// the props proxy makes the props object passed to setup() reactive
|
|
|
|
// so props change can be tracked by watchers
|
|
|
|
// only need to create it if setup() actually expects it
|
|
|
|
// it will be updated in resolveProps() on updates before render
|
|
|
|
const propsProxy = (instance.propsProxy = setup.length
|
|
|
|
? immutable(instance.props)
|
|
|
|
: null)
|
|
|
|
instance.state = observable(setup.call(proxy, propsProxy))
|
2019-05-28 19:36:15 +08:00
|
|
|
currentInstance = null
|
|
|
|
}
|
|
|
|
}
|
2019-05-28 17:19:47 +08:00
|
|
|
|
2019-05-29 11:36:16 +08:00
|
|
|
export function renderComponentRoot(instance: ComponentInstance): VNode {
|
2019-05-30 23:16:15 +08:00
|
|
|
const { type: Component, renderProxy } = instance
|
2019-05-29 11:36:16 +08:00
|
|
|
if (isFunction(Component)) {
|
|
|
|
return normalizeVNode(Component(instance))
|
2019-05-28 17:19:47 +08:00
|
|
|
} else {
|
2019-05-29 11:36:16 +08:00
|
|
|
if (__DEV__ && !Component.render) {
|
2019-05-28 17:19:47 +08:00
|
|
|
// TODO warn missing render
|
|
|
|
}
|
2019-05-30 23:16:15 +08:00
|
|
|
return normalizeVNode(
|
|
|
|
(Component.render as Function).call(renderProxy, instance)
|
|
|
|
)
|
2019-05-28 17:19:47 +08:00
|
|
|
}
|
2019-05-28 13:27:31 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
export function shouldUpdateComponent(
|
|
|
|
prevVNode: VNode,
|
|
|
|
nextVNode: VNode
|
|
|
|
): boolean {
|
2019-05-31 12:25:11 +08:00
|
|
|
const { props: prevProps, children: prevChildren } = prevVNode
|
|
|
|
const { props: nextProps, children: nextChildren, patchFlag } = nextVNode
|
2019-05-30 16:00:42 +08:00
|
|
|
if (patchFlag !== null) {
|
2019-05-31 12:25:11 +08:00
|
|
|
if (patchFlag & DYNAMIC_SLOTS) {
|
2019-05-30 16:00:42 +08:00
|
|
|
// slot content that references values that might have changed,
|
|
|
|
// e.g. in a v-for
|
|
|
|
return true
|
|
|
|
}
|
2019-05-31 12:25:11 +08:00
|
|
|
if (patchFlag & FULL_PROPS) {
|
|
|
|
// presence of this flag indicates props are always non-null
|
|
|
|
return hasPropsChanged(prevProps as Data, nextProps as Data)
|
|
|
|
} else if (patchFlag & PROPS) {
|
2019-05-30 16:00:42 +08:00
|
|
|
const dynamicProps = nextVNode.dynamicProps as string[]
|
|
|
|
for (let i = 0; i < dynamicProps.length; i++) {
|
|
|
|
const key = dynamicProps[i]
|
|
|
|
if ((nextProps as any)[key] !== (prevProps as any)[key]) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
2019-05-31 12:25:11 +08:00
|
|
|
// this path is only taken by manually written render functions
|
|
|
|
// so presence of any children leads to a forced update
|
|
|
|
if (prevChildren != null || nextChildren != null) {
|
|
|
|
return true
|
|
|
|
}
|
2019-05-30 16:00:42 +08:00
|
|
|
if (prevProps === nextProps) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if (prevProps === null) {
|
|
|
|
return nextProps !== null
|
|
|
|
}
|
|
|
|
if (nextProps === null) {
|
|
|
|
return prevProps !== null
|
|
|
|
}
|
2019-05-31 12:25:11 +08:00
|
|
|
return hasPropsChanged(prevProps, nextProps)
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
function hasPropsChanged(prevProps: Data, nextProps: Data): boolean {
|
|
|
|
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]) {
|
2019-05-28 13:27:31 +08:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|