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

181 lines
5.3 KiB
TypeScript
Raw Normal View History

2018-09-19 23:35:38 +08:00
import { VNodeFlags } from './flags'
import { EMPTY_OBJ } from './utils'
import { VNode, createFragment } from './vdom'
import { Component, MountedComponent, ComponentClass } from './component'
import { createTextVNode, cloneVNode } from './vdom'
import { initializeState } from './componentState'
import { initializeProps } from './componentProps'
import {
initializeComputed,
getComputedOptions,
teardownComputed
} from './componentComputed'
import { initializeWatch, teardownWatch } from './componentWatch'
import { Data, ComponentOptions } from './componentOptions'
import { createRenderProxy } from './componentProxy'
export function createComponentInstance(
vnode: VNode,
Component: ComponentClass,
parentComponent: MountedComponent | null
): MountedComponent {
const instance = (vnode.children = new Component()) as MountedComponent
instance.$parentVNode = vnode
// 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)
}
// TODO provide/inject
initializeProps(instance, vnode.data)
initializeState(instance)
initializeComputed(instance, getComputedOptions(Component))
initializeWatch(instance, instance.$options.watch)
instance.$slots = vnode.slots || EMPTY_OBJ
if (instance.created) {
instance.created.call(proxy)
}
return instance as MountedComponent
}
export function renderInstanceRoot(instance: MountedComponent) {
// TODO handle render error
return normalizeComponentRoot(
instance.render.call(instance.$proxy, instance.$props, instance.$slots),
instance.$parentVNode
)
}
export function teardownComponentInstance(instance: MountedComponent) {
const parentComponent = instance.$parent && instance.$parent._self
if (parentComponent && !parentComponent._destroyed) {
parentComponent.$children.splice(
parentComponent.$children.indexOf(instance.$proxy),
1
)
}
teardownComputed(instance)
teardownWatch(instance)
}
export function normalizeComponentRoot(
vnode: any,
componentVNode: VNode | null
): VNode {
if (vnode == null) {
vnode = createTextVNode('')
} else if (typeof vnode !== 'object') {
vnode = createTextVNode(vnode + '')
} else if (Array.isArray(vnode)) {
vnode = createFragment(vnode)
} else {
const { flags } = vnode
// parentVNode data merge down
if (
componentVNode &&
(flags & VNodeFlags.COMPONENT || flags & VNodeFlags.ELEMENT)
) {
const parentData = componentVNode.data || EMPTY_OBJ
const childData = vnode.data || EMPTY_OBJ
let extraData: any = null
for (const key in parentData) {
// class/style bindings on parentVNode are merged down to child
// component root.
if (key === 'class') {
;(extraData || (extraData = {})).class = childData.class
? [].concat(childData.class, parentData.class)
: parentData.class
} else if (key === 'style') {
;(extraData || (extraData = {})).style = childData.style
? [].concat(childData.style, parentData.style)
: parentData.style
} else if (key.startsWith('nativeOn')) {
// nativeOn* handlers are merged down to child root as native listeners
const event = 'on' + key.slice(8)
;(extraData || (extraData = {}))[event] = childData.event
? [].concat(childData.event, parentData[key])
: parentData[key]
}
}
if (extraData) {
vnode = cloneVNode(vnode, extraData)
}
if (vnode.el) {
vnode = cloneVNode(vnode)
}
if (flags & VNodeFlags.COMPONENT) {
vnode.parentVNode = componentVNode
}
} else if (vnode.el) {
vnode = cloneVNode(vnode)
}
}
return vnode
}
export function shouldUpdateFunctionalComponent(
prevProps: Data | null,
nextProps: Data | null
): 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 {
class ObjectComponent extends Component {
constructor() {
super()
this.$options = options
}
}
for (const key in options) {
const value = options[key]
if (typeof value === 'function') {
;(ObjectComponent.prototype as any)[key] = value
}
if (key === 'computed') {
const isGet = typeof value === 'function'
Object.defineProperty(ObjectComponent.prototype, key, {
configurable: true,
get: isGet ? value : value.get,
set: isGet ? undefined : value.set
})
}
}
return ObjectComponent as ComponentClass
}