2018-09-19 23:35:38 +08:00
|
|
|
import { VNodeFlags } from './flags'
|
|
|
|
import { EMPTY_OBJ } from './utils'
|
2018-09-20 11:19:25 +08:00
|
|
|
import { h } from './h'
|
2018-10-03 01:59:11 +08:00
|
|
|
import { VNode, MountedVNode, createFragment } from './vdom'
|
2018-10-09 23:37:24 +08:00
|
|
|
import { Component, ComponentInstance, ComponentClass } from './component'
|
2018-09-19 23:35:38 +08:00
|
|
|
import { createTextVNode, cloneVNode } from './vdom'
|
|
|
|
import { initializeState } from './componentState'
|
|
|
|
import { initializeProps } from './componentProps'
|
|
|
|
import {
|
|
|
|
initializeComputed,
|
2018-10-10 08:22:29 +08:00
|
|
|
resolveComputedOptions,
|
2018-09-19 23:35:38 +08:00
|
|
|
teardownComputed
|
|
|
|
} from './componentComputed'
|
|
|
|
import { initializeWatch, teardownWatch } from './componentWatch'
|
2018-10-09 23:37:24 +08:00
|
|
|
import { ComponentOptions } from './componentOptions'
|
2018-09-19 23:35:38 +08:00
|
|
|
import { createRenderProxy } from './componentProxy'
|
2018-09-24 08:30:26 +08:00
|
|
|
import { handleError, ErrorTypes } from './errorHandling'
|
2018-09-19 23:35:38 +08:00
|
|
|
|
|
|
|
export function createComponentInstance(
|
|
|
|
vnode: VNode,
|
|
|
|
Component: ComponentClass,
|
2018-10-11 01:13:27 +08:00
|
|
|
contextVNode: MountedVNode | null
|
2018-10-09 23:37:24 +08:00
|
|
|
): ComponentInstance {
|
|
|
|
const instance = (vnode.children = new Component()) as ComponentInstance
|
2018-10-03 01:59:11 +08:00
|
|
|
instance.$parentVNode = vnode as MountedVNode
|
2018-09-19 23:35:38 +08:00
|
|
|
|
|
|
|
// renderProxy
|
|
|
|
const proxy = (instance.$proxy = createRenderProxy(instance))
|
|
|
|
|
|
|
|
// pointer management
|
2018-10-11 01:13:27 +08:00
|
|
|
if (contextVNode !== null) {
|
|
|
|
// locate first non-functional parent
|
|
|
|
while (
|
|
|
|
contextVNode !== null &&
|
|
|
|
contextVNode.flags & VNodeFlags.COMPONENT_FUNCTIONAL &&
|
|
|
|
contextVNode.contextVNode !== null
|
|
|
|
) {
|
|
|
|
contextVNode = contextVNode.contextVNode as any
|
|
|
|
}
|
|
|
|
const parentComponent = (contextVNode as VNode)
|
|
|
|
.children as ComponentInstance
|
2018-09-19 23:35:38 +08:00
|
|
|
instance.$parent = parentComponent.$proxy
|
|
|
|
instance.$root = parentComponent.$root
|
|
|
|
parentComponent.$children.push(proxy)
|
|
|
|
} else {
|
|
|
|
instance.$root = proxy
|
|
|
|
}
|
|
|
|
|
|
|
|
// lifecycle
|
|
|
|
if (instance.beforeCreate) {
|
|
|
|
instance.beforeCreate.call(proxy)
|
|
|
|
}
|
2018-10-10 08:22:29 +08:00
|
|
|
initializeProps(instance, Component.props, vnode.data)
|
2018-09-19 23:35:38 +08:00
|
|
|
initializeState(instance)
|
2018-10-10 08:22:29 +08:00
|
|
|
initializeComputed(instance, Component.computed)
|
|
|
|
initializeWatch(instance, Component.watch)
|
2018-09-19 23:35:38 +08:00
|
|
|
instance.$slots = vnode.slots || EMPTY_OBJ
|
|
|
|
if (instance.created) {
|
|
|
|
instance.created.call(proxy)
|
|
|
|
}
|
|
|
|
|
2018-10-09 23:37:24 +08:00
|
|
|
return instance as ComponentInstance
|
2018-09-19 23:35:38 +08:00
|
|
|
}
|
|
|
|
|
2018-10-09 23:37:24 +08:00
|
|
|
export function renderInstanceRoot(instance: ComponentInstance): VNode {
|
2018-09-24 08:30:26 +08:00
|
|
|
let vnode
|
|
|
|
try {
|
2018-10-10 09:10:30 +08:00
|
|
|
vnode = instance.render.call(
|
|
|
|
instance.$proxy,
|
|
|
|
instance.$props,
|
|
|
|
instance.$slots,
|
|
|
|
instance.$attrs
|
|
|
|
)
|
2018-09-24 08:30:26 +08:00
|
|
|
} catch (e1) {
|
|
|
|
handleError(e1, instance, ErrorTypes.RENDER)
|
|
|
|
if (__DEV__ && instance.renderError) {
|
|
|
|
try {
|
|
|
|
vnode = instance.renderError.call(instance.$proxy, e1)
|
|
|
|
} catch (e2) {
|
|
|
|
handleError(e2, instance, ErrorTypes.RENDER_ERROR)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-10-10 09:10:30 +08:00
|
|
|
return normalizeComponentRoot(vnode, instance.$parentVNode)
|
2018-09-19 23:35:38 +08:00
|
|
|
}
|
|
|
|
|
2018-10-09 23:37:24 +08:00
|
|
|
export function teardownComponentInstance(instance: ComponentInstance) {
|
2018-09-27 05:10:34 +08:00
|
|
|
if (instance._unmounted) {
|
|
|
|
return
|
|
|
|
}
|
2018-09-19 23:35:38 +08:00
|
|
|
const parentComponent = instance.$parent && instance.$parent._self
|
2018-09-27 05:10:34 +08:00
|
|
|
if (parentComponent && !parentComponent._unmounted) {
|
2018-09-19 23:35:38 +08:00
|
|
|
parentComponent.$children.splice(
|
|
|
|
parentComponent.$children.indexOf(instance.$proxy),
|
|
|
|
1
|
|
|
|
)
|
|
|
|
}
|
|
|
|
teardownComputed(instance)
|
|
|
|
teardownWatch(instance)
|
|
|
|
}
|
|
|
|
|
|
|
|
export function normalizeComponentRoot(
|
|
|
|
vnode: any,
|
2018-10-10 09:10:30 +08:00
|
|
|
componentVNode: VNode | null
|
2018-09-19 23:35:38 +08:00
|
|
|
): VNode {
|
|
|
|
if (vnode == null) {
|
|
|
|
vnode = createTextVNode('')
|
|
|
|
} else if (typeof vnode !== 'object') {
|
|
|
|
vnode = createTextVNode(vnode + '')
|
|
|
|
} else if (Array.isArray(vnode)) {
|
2018-09-26 05:49:47 +08:00
|
|
|
if (vnode.length === 1) {
|
2018-10-10 09:10:30 +08:00
|
|
|
vnode = normalizeComponentRoot(vnode[0], componentVNode)
|
2018-09-26 05:49:47 +08:00
|
|
|
} else {
|
|
|
|
vnode = createFragment(vnode)
|
|
|
|
}
|
2018-09-19 23:35:38 +08:00
|
|
|
} else {
|
2018-09-27 05:10:34 +08:00
|
|
|
const { el, flags } = vnode
|
2018-09-19 23:35:38 +08:00
|
|
|
if (
|
|
|
|
componentVNode &&
|
|
|
|
(flags & VNodeFlags.COMPONENT || flags & VNodeFlags.ELEMENT)
|
|
|
|
) {
|
2018-10-10 09:10:30 +08:00
|
|
|
if (el) {
|
2018-09-19 23:35:38 +08:00
|
|
|
vnode = cloneVNode(vnode)
|
|
|
|
}
|
|
|
|
if (flags & VNodeFlags.COMPONENT) {
|
|
|
|
vnode.parentVNode = componentVNode
|
|
|
|
}
|
2018-09-27 05:10:34 +08:00
|
|
|
} else if (el) {
|
2018-09-19 23:35:38 +08:00
|
|
|
vnode = cloneVNode(vnode)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return vnode
|
|
|
|
}
|
|
|
|
|
|
|
|
export function shouldUpdateFunctionalComponent(
|
2018-10-09 23:37:24 +08:00
|
|
|
prevProps: Record<string, any> | null,
|
|
|
|
nextProps: Record<string, any> | null
|
2018-09-19 23:35:38 +08:00
|
|
|
): boolean {
|
|
|
|
if (prevProps === nextProps) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if (prevProps === null) {
|
|
|
|
return nextProps !== null
|
|
|
|
}
|
|
|
|
if (nextProps === null) {
|
|
|
|
return prevProps !== null
|
|
|
|
}
|
|
|
|
let shouldUpdate = true
|
|
|
|
const nextKeys = Object.keys(nextProps)
|
|
|
|
if (nextKeys.length === Object.keys(prevProps).length) {
|
|
|
|
shouldUpdate = false
|
|
|
|
for (let i = 0; i < nextKeys.length; i++) {
|
|
|
|
const key = nextKeys[i]
|
|
|
|
if (nextProps[key] !== prevProps[key]) {
|
|
|
|
shouldUpdate = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return shouldUpdate
|
|
|
|
}
|
|
|
|
|
|
|
|
export function createComponentClassFromOptions(
|
|
|
|
options: ComponentOptions
|
|
|
|
): ComponentClass {
|
2018-10-09 23:37:24 +08:00
|
|
|
class AnonymousComponent extends Component {
|
2018-10-10 08:22:29 +08:00
|
|
|
static options = options
|
2018-09-19 23:35:38 +08:00
|
|
|
}
|
2018-10-09 23:37:24 +08:00
|
|
|
const proto = AnonymousComponent.prototype as any
|
2018-09-19 23:35:38 +08:00
|
|
|
for (const key in options) {
|
|
|
|
const value = options[key]
|
2018-10-09 23:37:24 +08:00
|
|
|
// name -> displayName
|
2018-10-10 08:22:29 +08:00
|
|
|
if (key === 'name') {
|
|
|
|
AnonymousComponent.displayName = options.name
|
|
|
|
} else if (typeof value === 'function') {
|
|
|
|
if (__COMPAT__) {
|
|
|
|
if (key === 'render') {
|
|
|
|
proto[key] = function() {
|
|
|
|
return value.call(this, h)
|
|
|
|
}
|
|
|
|
} else if (key === 'beforeDestroy') {
|
|
|
|
proto.beforeUnmount = value
|
|
|
|
} else if (key === 'destroyed') {
|
|
|
|
proto.unmounted = value
|
2018-10-10 09:10:30 +08:00
|
|
|
} else {
|
|
|
|
proto[key] = value
|
2018-10-09 23:37:24 +08:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
proto[key] = value
|
|
|
|
}
|
2018-10-10 08:22:29 +08:00
|
|
|
} else if (key === 'computed') {
|
|
|
|
AnonymousComponent.computed = value
|
|
|
|
for (const computedKey in value) {
|
|
|
|
const computed = value[computedKey]
|
|
|
|
const isGet = typeof computed === 'function'
|
|
|
|
Object.defineProperty(proto, computedKey, {
|
|
|
|
configurable: true,
|
|
|
|
get: isGet ? computed : computed.get,
|
|
|
|
set: isGet ? undefined : computed.set
|
|
|
|
})
|
|
|
|
}
|
|
|
|
} else if (key === 'methods') {
|
2018-09-20 11:43:27 +08:00
|
|
|
for (const method in value) {
|
2018-10-09 23:37:24 +08:00
|
|
|
if (__DEV__ && proto.hasOwnProperty(method)) {
|
|
|
|
console.warn(
|
|
|
|
`Object syntax contains method name that conflicts with ` +
|
|
|
|
`lifecycle hook: "${method}"`
|
|
|
|
)
|
|
|
|
}
|
|
|
|
proto[method] = value[method]
|
2018-09-20 11:43:27 +08:00
|
|
|
}
|
2018-10-10 08:22:29 +08:00
|
|
|
} else {
|
|
|
|
;(AnonymousComponent as any)[key] = value
|
2018-09-20 11:43:27 +08:00
|
|
|
}
|
2018-09-19 23:35:38 +08:00
|
|
|
}
|
2018-10-09 23:37:24 +08:00
|
|
|
return AnonymousComponent as ComponentClass
|
2018-09-19 23:35:38 +08:00
|
|
|
}
|
2018-10-10 08:22:29 +08:00
|
|
|
|
|
|
|
export function resolveComponentOptions(
|
|
|
|
Component: ComponentClass
|
|
|
|
): ComponentOptions {
|
|
|
|
const keys = Object.keys(Component)
|
|
|
|
const options = {} as any
|
|
|
|
for (let i = 0; i < keys.length; i++) {
|
|
|
|
const key = keys[i]
|
|
|
|
options[key] = (Component as any)[key]
|
|
|
|
}
|
|
|
|
Component.computed = options.computed = resolveComputedOptions(Component)
|
|
|
|
Component.options = options
|
|
|
|
return options
|
|
|
|
}
|