vue3-yuanma/packages/core/src/componentUtils.ts

221 lines
6.1 KiB
TypeScript
Raw Normal View History

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'
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-09 23:37:24 +08:00
parentComponent: ComponentInstance | null
): ComponentInstance {
const instance = (vnode.children = new Component()) as ComponentInstance
instance.$parentVNode = vnode as MountedVNode
2018-09-19 23:35:38 +08:00
// renderProxy
const proxy = (instance.$proxy = createRenderProxy(instance))
// pointer management
if (parentComponent) {
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 {
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)
}
}
}
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,
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) {
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)
) {
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
} 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
}