refactor: adjust files
This commit is contained in:
parent
cbf95c642e
commit
77dae71062
@ -11,7 +11,7 @@ import { setupWatcher } from './componentWatch'
|
|||||||
import { ReactiveEffect, DebuggerEvent, ComputedGetter } from '@vue/observer'
|
import { ReactiveEffect, DebuggerEvent, ComputedGetter } from '@vue/observer'
|
||||||
import { nextTick } from '@vue/scheduler'
|
import { nextTick } from '@vue/scheduler'
|
||||||
import { ErrorTypes } from './errorHandling'
|
import { ErrorTypes } from './errorHandling'
|
||||||
import { initializeComponentInstance } from './componentUtils'
|
import { initializeComponentInstance } from './componentInstance'
|
||||||
import { EventEmitter, invokeListeners } from './optional/eventEmitter'
|
import { EventEmitter, invokeListeners } from './optional/eventEmitter'
|
||||||
import { warn } from './warning'
|
import { warn } from './warning'
|
||||||
|
|
||||||
|
104
packages/runtime-core/src/componentInstance.ts
Normal file
104
packages/runtime-core/src/componentInstance.ts
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
import { VNode, MountedVNode } from './vdom'
|
||||||
|
import { Component, ComponentInstance, ComponentClass } from './component'
|
||||||
|
import { initializeState } from './componentState'
|
||||||
|
import { initializeProps } from './componentProps'
|
||||||
|
import { initializeWatch, teardownWatch } from './componentWatch'
|
||||||
|
import { initializeComputed, teardownComputed } from './componentComputed'
|
||||||
|
import { createRenderProxy } from './componentProxy'
|
||||||
|
import { resolveComponentOptionsFromClass } from './componentOptions'
|
||||||
|
import { VNodeFlags } from './flags'
|
||||||
|
import { ErrorTypes, callLifecycleHookWithHandler } from './errorHandling'
|
||||||
|
import { stop } from '@vue/observer'
|
||||||
|
import { EMPTY_OBJ } from '@vue/shared'
|
||||||
|
|
||||||
|
let currentVNode: VNode | null = null
|
||||||
|
let currentContextVNode: VNode | null = null
|
||||||
|
|
||||||
|
export function createComponentInstance<T extends Component>(
|
||||||
|
vnode: VNode
|
||||||
|
): ComponentInstance {
|
||||||
|
// component instance creation is done in two steps.
|
||||||
|
// first, `initializeComponentInstance` is called inside base component
|
||||||
|
// constructor as the instance is created so that the extended component's
|
||||||
|
// constructor has access to certain properties and most importantly,
|
||||||
|
// this.$props.
|
||||||
|
// we are storing the vnodes in variables here so that there's no need to
|
||||||
|
// always pass args in super()
|
||||||
|
currentVNode = vnode
|
||||||
|
currentContextVNode = vnode.contextVNode
|
||||||
|
const Component = vnode.tag as ComponentClass
|
||||||
|
const instance = (vnode.children = new Component() as ComponentInstance)
|
||||||
|
|
||||||
|
// then we finish the initialization by collecting properties set on the
|
||||||
|
// instance
|
||||||
|
const {
|
||||||
|
$proxy,
|
||||||
|
$options: { created, computed, watch }
|
||||||
|
} = instance
|
||||||
|
initializeState(instance, !Component.fromOptions)
|
||||||
|
initializeComputed(instance, computed)
|
||||||
|
initializeWatch(instance, watch)
|
||||||
|
instance.$slots = currentVNode.slots || EMPTY_OBJ
|
||||||
|
|
||||||
|
if (created) {
|
||||||
|
callLifecycleHookWithHandler(created, $proxy, ErrorTypes.CREATED)
|
||||||
|
}
|
||||||
|
|
||||||
|
currentVNode = currentContextVNode = null
|
||||||
|
return instance
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is called inside the base component's constructor
|
||||||
|
// it initializes all the way up to props so that they are available
|
||||||
|
// inside the extended component's constructor
|
||||||
|
export function initializeComponentInstance(instance: ComponentInstance) {
|
||||||
|
if (__DEV__ && currentVNode === null) {
|
||||||
|
throw new Error(
|
||||||
|
`Component classes are not meant to be manually instantiated.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
instance.$options = resolveComponentOptionsFromClass(instance.constructor)
|
||||||
|
instance.$parentVNode = currentVNode as MountedVNode
|
||||||
|
|
||||||
|
// renderProxy
|
||||||
|
const proxy = (instance.$proxy = createRenderProxy(instance))
|
||||||
|
|
||||||
|
// parent chain management
|
||||||
|
if (currentContextVNode !== null) {
|
||||||
|
// locate first non-functional parent
|
||||||
|
while (currentContextVNode !== null) {
|
||||||
|
if ((currentContextVNode.flags & VNodeFlags.COMPONENT_STATEFUL) > 0) {
|
||||||
|
const parentComponent = (currentContextVNode as VNode)
|
||||||
|
.children as ComponentInstance
|
||||||
|
instance.$parent = parentComponent.$proxy
|
||||||
|
instance.$root = parentComponent.$root
|
||||||
|
parentComponent.$children.push(proxy)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
currentContextVNode = currentContextVNode.contextVNode
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
instance.$root = proxy
|
||||||
|
}
|
||||||
|
|
||||||
|
// beforeCreate hook is called right in the constructor
|
||||||
|
const { beforeCreate, props } = instance.$options
|
||||||
|
if (beforeCreate) {
|
||||||
|
callLifecycleHookWithHandler(beforeCreate, proxy, ErrorTypes.BEFORE_CREATE)
|
||||||
|
}
|
||||||
|
initializeProps(instance, props, (currentVNode as VNode).data)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function teardownComponentInstance(instance: ComponentInstance) {
|
||||||
|
const parentComponent = instance.$parent && instance.$parent._self
|
||||||
|
if (parentComponent && !parentComponent._unmounted) {
|
||||||
|
parentComponent.$children.splice(
|
||||||
|
parentComponent.$children.indexOf(instance.$proxy),
|
||||||
|
1
|
||||||
|
)
|
||||||
|
}
|
||||||
|
stop(instance._update)
|
||||||
|
teardownComputed(instance)
|
||||||
|
teardownWatch(instance)
|
||||||
|
}
|
@ -7,6 +7,8 @@ import {
|
|||||||
} from './component'
|
} from './component'
|
||||||
import { isArray, isObject, isFunction } from '@vue/shared'
|
import { isArray, isObject, isFunction } from '@vue/shared'
|
||||||
import { normalizePropsOptions } from './componentProps'
|
import { normalizePropsOptions } from './componentProps'
|
||||||
|
import { warn } from './warning'
|
||||||
|
import { h } from './h'
|
||||||
|
|
||||||
export type Data = Record<string, any>
|
export type Data = Record<string, any>
|
||||||
|
|
||||||
@ -170,6 +172,65 @@ export function resolveComponentOptionsFromClass(
|
|||||||
return options
|
return options
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function createComponentClassFromOptions(
|
||||||
|
options: ComponentOptions
|
||||||
|
): ComponentClass {
|
||||||
|
class AnonymousComponent extends Component {
|
||||||
|
static options = options
|
||||||
|
// indicate this component was created from options
|
||||||
|
static fromOptions = true
|
||||||
|
}
|
||||||
|
const proto = AnonymousComponent.prototype as any
|
||||||
|
for (const key in options) {
|
||||||
|
const value = options[key]
|
||||||
|
if (key === 'render') {
|
||||||
|
if (__COMPAT__) {
|
||||||
|
options.render = function() {
|
||||||
|
return value.call(this, h)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// so that we can call instance.render directly
|
||||||
|
proto.render = options.render
|
||||||
|
} else if (key === 'computed') {
|
||||||
|
// create computed setters on prototype
|
||||||
|
// (getters are handled by the render proxy)
|
||||||
|
for (const computedKey in value) {
|
||||||
|
const computed = value[computedKey]
|
||||||
|
const set = isObject(computed) && computed.set
|
||||||
|
if (set) {
|
||||||
|
Object.defineProperty(proto, computedKey, {
|
||||||
|
configurable: true,
|
||||||
|
set
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (key === 'methods') {
|
||||||
|
for (const method in value) {
|
||||||
|
if (__DEV__ && proto.hasOwnProperty(method)) {
|
||||||
|
warn(
|
||||||
|
`Object syntax contains method name that conflicts with ` +
|
||||||
|
`lifecycle hook: "${method}"`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
proto[method] = value[method]
|
||||||
|
}
|
||||||
|
} else if (__COMPAT__) {
|
||||||
|
if (key === 'name') {
|
||||||
|
options.displayName = value
|
||||||
|
} else if (key === 'render') {
|
||||||
|
options.render = function() {
|
||||||
|
return value.call(this, h)
|
||||||
|
}
|
||||||
|
} else if (key === 'beforeDestroy') {
|
||||||
|
options.beforeUnmount = value
|
||||||
|
} else if (key === 'destroyed') {
|
||||||
|
options.unmounted = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return AnonymousComponent as ComponentClass
|
||||||
|
}
|
||||||
|
|
||||||
export function mergeComponentOptions(to: any, from: any): ComponentOptions {
|
export function mergeComponentOptions(to: any, from: any): ComponentOptions {
|
||||||
const res: any = Object.assign({}, to)
|
const res: any = Object.assign({}, to)
|
||||||
if (from.mixins) {
|
if (from.mixins) {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { ComponentInstance } from './component'
|
import { ComponentInstance } from './component'
|
||||||
import { isFunction, isReservedKey } from '@vue/shared'
|
import { isFunction, isReservedKey } from '@vue/shared'
|
||||||
import { warn } from './warning'
|
import { warn } from './warning'
|
||||||
import { isRendering } from './componentUtils'
|
import { isRendering } from './componentRenderUtils'
|
||||||
import { isObservable } from '@vue/observer'
|
import { isObservable } from '@vue/observer'
|
||||||
import { reservedMethods } from './componentOptions'
|
import { reservedMethods } from './componentOptions'
|
||||||
|
|
||||||
|
165
packages/runtime-core/src/componentRenderUtils.ts
Normal file
165
packages/runtime-core/src/componentRenderUtils.ts
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
import { VNode, createFragment, createTextVNode, cloneVNode } from './vdom'
|
||||||
|
import { ComponentInstance, FunctionalComponent } from './component'
|
||||||
|
import { resolveProps } from './componentProps'
|
||||||
|
import { handleError, ErrorTypes } from './errorHandling'
|
||||||
|
import { VNodeFlags, ChildrenFlags } from './flags'
|
||||||
|
import { EMPTY_OBJ, isArray, isObject } from '@vue/shared'
|
||||||
|
import { setCurrentInstance, unsetCurrentInstance } from './experimental/hooks'
|
||||||
|
|
||||||
|
export let isRendering = false
|
||||||
|
|
||||||
|
export function renderInstanceRoot(instance: ComponentInstance): VNode {
|
||||||
|
let vnode
|
||||||
|
const {
|
||||||
|
$options: { hooks },
|
||||||
|
render,
|
||||||
|
$proxy,
|
||||||
|
$props,
|
||||||
|
$slots,
|
||||||
|
$attrs,
|
||||||
|
$parentVNode
|
||||||
|
} = instance
|
||||||
|
try {
|
||||||
|
setCurrentInstance(instance)
|
||||||
|
if (hooks) {
|
||||||
|
instance._hookProps = hooks.call($proxy, $props) || null
|
||||||
|
}
|
||||||
|
if (__DEV__) {
|
||||||
|
isRendering = true
|
||||||
|
}
|
||||||
|
vnode = render.call($proxy, $props, $slots, $attrs, $parentVNode)
|
||||||
|
if (__DEV__) {
|
||||||
|
isRendering = false
|
||||||
|
}
|
||||||
|
unsetCurrentInstance()
|
||||||
|
} catch (err) {
|
||||||
|
handleError(err, instance, ErrorTypes.RENDER)
|
||||||
|
}
|
||||||
|
return normalizeComponentRoot(vnode, $parentVNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function renderFunctionalRoot(vnode: VNode): VNode {
|
||||||
|
const render = vnode.tag as FunctionalComponent
|
||||||
|
const { 0: props, 1: attrs } = resolveProps(vnode.data, render.props)
|
||||||
|
let subTree
|
||||||
|
try {
|
||||||
|
subTree = render(props, vnode.slots || EMPTY_OBJ, attrs, vnode)
|
||||||
|
} catch (err) {
|
||||||
|
handleError(err, vnode, ErrorTypes.RENDER)
|
||||||
|
}
|
||||||
|
return normalizeComponentRoot(subTree, vnode)
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeComponentRoot(
|
||||||
|
vnode: any,
|
||||||
|
componentVNode: VNode | null
|
||||||
|
): VNode {
|
||||||
|
if (vnode == null) {
|
||||||
|
vnode = createTextVNode('')
|
||||||
|
} else if (!isObject(vnode)) {
|
||||||
|
vnode = createTextVNode(vnode + '')
|
||||||
|
} else if (isArray(vnode)) {
|
||||||
|
if (vnode.length === 1) {
|
||||||
|
vnode = normalizeComponentRoot(vnode[0], componentVNode)
|
||||||
|
} else {
|
||||||
|
vnode = createFragment(vnode)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const { el, flags } = vnode
|
||||||
|
if (
|
||||||
|
componentVNode &&
|
||||||
|
(flags & VNodeFlags.COMPONENT || flags & VNodeFlags.ELEMENT)
|
||||||
|
) {
|
||||||
|
if (el) {
|
||||||
|
vnode = cloneVNode(vnode as VNode)
|
||||||
|
}
|
||||||
|
if (flags & VNodeFlags.COMPONENT) {
|
||||||
|
vnode.parentVNode = componentVNode
|
||||||
|
}
|
||||||
|
} else if (el) {
|
||||||
|
vnode = cloneVNode(vnode as VNode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return vnode
|
||||||
|
}
|
||||||
|
|
||||||
|
export function shouldUpdateComponent(
|
||||||
|
prevVNode: VNode,
|
||||||
|
nextVNode: VNode
|
||||||
|
): boolean {
|
||||||
|
const { data: prevProps, childFlags: prevChildFlags } = prevVNode
|
||||||
|
const { data: nextProps, childFlags: nextChildFlags } = nextVNode
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// DEV only
|
||||||
|
export function getReasonForComponentUpdate(
|
||||||
|
prevVNode: VNode,
|
||||||
|
nextVNode: VNode
|
||||||
|
): any {
|
||||||
|
const reasons = []
|
||||||
|
const { childFlags: prevChildFlags } = prevVNode
|
||||||
|
const { childFlags: nextChildFlags } = nextVNode
|
||||||
|
if (
|
||||||
|
prevChildFlags !== nextChildFlags ||
|
||||||
|
(nextChildFlags & ChildrenFlags.DYNAMIC_SLOTS) > 0
|
||||||
|
) {
|
||||||
|
reasons.push({
|
||||||
|
type: `slots may have changed`,
|
||||||
|
tip: `use function slots + $stable: true to avoid slot-triggered child updates.`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const prevProps = prevVNode.data || EMPTY_OBJ
|
||||||
|
const nextProps = nextVNode.data || EMPTY_OBJ
|
||||||
|
for (const key in nextProps) {
|
||||||
|
if (nextProps[key] !== prevProps[key]) {
|
||||||
|
reasons.push({
|
||||||
|
type: 'prop changed',
|
||||||
|
key,
|
||||||
|
value: nextProps[key],
|
||||||
|
oldValue: prevProps[key]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const key in prevProps) {
|
||||||
|
if (!(key in nextProps)) {
|
||||||
|
reasons.push({
|
||||||
|
type: 'prop changed',
|
||||||
|
key,
|
||||||
|
value: undefined,
|
||||||
|
oldValue: prevProps[key]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
type: 'triggered by parent',
|
||||||
|
reasons
|
||||||
|
}
|
||||||
|
}
|
@ -1,337 +0,0 @@
|
|||||||
import { VNodeFlags, ChildrenFlags } from './flags'
|
|
||||||
import { EMPTY_OBJ, isArray, isObject } from '@vue/shared'
|
|
||||||
import { h } from './h'
|
|
||||||
import { VNode, MountedVNode, createFragment } from './vdom'
|
|
||||||
import {
|
|
||||||
Component,
|
|
||||||
ComponentInstance,
|
|
||||||
ComponentClass,
|
|
||||||
FunctionalComponent
|
|
||||||
} from './component'
|
|
||||||
import { createTextVNode, cloneVNode } from './vdom'
|
|
||||||
import { initializeState } from './componentState'
|
|
||||||
import { initializeProps, resolveProps } from './componentProps'
|
|
||||||
import { initializeComputed, teardownComputed } from './componentComputed'
|
|
||||||
import { initializeWatch, teardownWatch } from './componentWatch'
|
|
||||||
import {
|
|
||||||
ComponentOptions,
|
|
||||||
resolveComponentOptionsFromClass
|
|
||||||
} from './componentOptions'
|
|
||||||
import { createRenderProxy } from './componentProxy'
|
|
||||||
import {
|
|
||||||
handleError,
|
|
||||||
ErrorTypes,
|
|
||||||
callLifecycleHookWithHandler
|
|
||||||
} from './errorHandling'
|
|
||||||
import { warn } from './warning'
|
|
||||||
import { setCurrentInstance, unsetCurrentInstance } from './experimental/hooks'
|
|
||||||
import { stop } from '@vue/observer'
|
|
||||||
|
|
||||||
let currentVNode: VNode | null = null
|
|
||||||
let currentContextVNode: VNode | null = null
|
|
||||||
|
|
||||||
export function createComponentInstance<T extends Component>(
|
|
||||||
vnode: VNode
|
|
||||||
): ComponentInstance {
|
|
||||||
// component instance creation is done in two steps.
|
|
||||||
// first, `initializeComponentInstance` is called inside base component
|
|
||||||
// constructor as the instance is created so that the extended component's
|
|
||||||
// constructor has access to certain properties and most importantly,
|
|
||||||
// this.$props.
|
|
||||||
// we are storing the vnodes in variables here so that there's no need to
|
|
||||||
// always pass args in super()
|
|
||||||
currentVNode = vnode
|
|
||||||
currentContextVNode = vnode.contextVNode
|
|
||||||
const Component = vnode.tag as ComponentClass
|
|
||||||
const instance = (vnode.children = new Component() as ComponentInstance)
|
|
||||||
|
|
||||||
// then we finish the initialization by collecting properties set on the
|
|
||||||
// instance
|
|
||||||
const {
|
|
||||||
$proxy,
|
|
||||||
$options: { created, computed, watch }
|
|
||||||
} = instance
|
|
||||||
initializeState(instance, !Component.fromOptions)
|
|
||||||
initializeComputed(instance, computed)
|
|
||||||
initializeWatch(instance, watch)
|
|
||||||
instance.$slots = currentVNode.slots || EMPTY_OBJ
|
|
||||||
|
|
||||||
if (created) {
|
|
||||||
callLifecycleHookWithHandler(created, $proxy, ErrorTypes.CREATED)
|
|
||||||
}
|
|
||||||
|
|
||||||
currentVNode = currentContextVNode = null
|
|
||||||
return instance
|
|
||||||
}
|
|
||||||
|
|
||||||
// this is called inside the base component's constructor
|
|
||||||
// it initializes all the way up to props so that they are available
|
|
||||||
// inside the extended component's constructor
|
|
||||||
export function initializeComponentInstance(instance: ComponentInstance) {
|
|
||||||
if (__DEV__ && currentVNode === null) {
|
|
||||||
throw new Error(
|
|
||||||
`Component classes are not meant to be manually instantiated.`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
instance.$options = resolveComponentOptionsFromClass(instance.constructor)
|
|
||||||
instance.$parentVNode = currentVNode as MountedVNode
|
|
||||||
|
|
||||||
// renderProxy
|
|
||||||
const proxy = (instance.$proxy = createRenderProxy(instance))
|
|
||||||
|
|
||||||
// parent chain management
|
|
||||||
if (currentContextVNode !== null) {
|
|
||||||
// locate first non-functional parent
|
|
||||||
while (currentContextVNode !== null) {
|
|
||||||
if ((currentContextVNode.flags & VNodeFlags.COMPONENT_STATEFUL) > 0) {
|
|
||||||
const parentComponent = (currentContextVNode as VNode)
|
|
||||||
.children as ComponentInstance
|
|
||||||
instance.$parent = parentComponent.$proxy
|
|
||||||
instance.$root = parentComponent.$root
|
|
||||||
parentComponent.$children.push(proxy)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
currentContextVNode = currentContextVNode.contextVNode
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
instance.$root = proxy
|
|
||||||
}
|
|
||||||
|
|
||||||
// beforeCreate hook is called right in the constructor
|
|
||||||
const { beforeCreate, props } = instance.$options
|
|
||||||
if (beforeCreate) {
|
|
||||||
callLifecycleHookWithHandler(beforeCreate, proxy, ErrorTypes.BEFORE_CREATE)
|
|
||||||
}
|
|
||||||
initializeProps(instance, props, (currentVNode as VNode).data)
|
|
||||||
}
|
|
||||||
|
|
||||||
export let isRendering = false
|
|
||||||
|
|
||||||
export function renderInstanceRoot(instance: ComponentInstance): VNode {
|
|
||||||
let vnode
|
|
||||||
const {
|
|
||||||
$options: { hooks },
|
|
||||||
render,
|
|
||||||
$proxy,
|
|
||||||
$props,
|
|
||||||
$slots,
|
|
||||||
$attrs,
|
|
||||||
$parentVNode
|
|
||||||
} = instance
|
|
||||||
try {
|
|
||||||
setCurrentInstance(instance)
|
|
||||||
if (hooks) {
|
|
||||||
instance._hookProps = hooks.call($proxy, $props) || null
|
|
||||||
}
|
|
||||||
if (__DEV__) {
|
|
||||||
isRendering = true
|
|
||||||
}
|
|
||||||
vnode = render.call($proxy, $props, $slots, $attrs, $parentVNode)
|
|
||||||
if (__DEV__) {
|
|
||||||
isRendering = false
|
|
||||||
}
|
|
||||||
unsetCurrentInstance()
|
|
||||||
} catch (err) {
|
|
||||||
handleError(err, instance, ErrorTypes.RENDER)
|
|
||||||
}
|
|
||||||
return normalizeComponentRoot(vnode, $parentVNode)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function renderFunctionalRoot(vnode: VNode): VNode {
|
|
||||||
const render = vnode.tag as FunctionalComponent
|
|
||||||
const { 0: props, 1: attrs } = resolveProps(vnode.data, render.props)
|
|
||||||
let subTree
|
|
||||||
try {
|
|
||||||
subTree = render(props, vnode.slots || EMPTY_OBJ, attrs, vnode)
|
|
||||||
} catch (err) {
|
|
||||||
handleError(err, vnode, ErrorTypes.RENDER)
|
|
||||||
}
|
|
||||||
return normalizeComponentRoot(subTree, vnode)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function teardownComponentInstance(instance: ComponentInstance) {
|
|
||||||
const parentComponent = instance.$parent && instance.$parent._self
|
|
||||||
if (parentComponent && !parentComponent._unmounted) {
|
|
||||||
parentComponent.$children.splice(
|
|
||||||
parentComponent.$children.indexOf(instance.$proxy),
|
|
||||||
1
|
|
||||||
)
|
|
||||||
}
|
|
||||||
stop(instance._update)
|
|
||||||
teardownComputed(instance)
|
|
||||||
teardownWatch(instance)
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizeComponentRoot(
|
|
||||||
vnode: any,
|
|
||||||
componentVNode: VNode | null
|
|
||||||
): VNode {
|
|
||||||
if (vnode == null) {
|
|
||||||
vnode = createTextVNode('')
|
|
||||||
} else if (!isObject(vnode)) {
|
|
||||||
vnode = createTextVNode(vnode + '')
|
|
||||||
} else if (isArray(vnode)) {
|
|
||||||
if (vnode.length === 1) {
|
|
||||||
vnode = normalizeComponentRoot(vnode[0], componentVNode)
|
|
||||||
} else {
|
|
||||||
vnode = createFragment(vnode)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const { el, flags } = vnode
|
|
||||||
if (
|
|
||||||
componentVNode &&
|
|
||||||
(flags & VNodeFlags.COMPONENT || flags & VNodeFlags.ELEMENT)
|
|
||||||
) {
|
|
||||||
if (el) {
|
|
||||||
vnode = cloneVNode(vnode as VNode)
|
|
||||||
}
|
|
||||||
if (flags & VNodeFlags.COMPONENT) {
|
|
||||||
vnode.parentVNode = componentVNode
|
|
||||||
}
|
|
||||||
} else if (el) {
|
|
||||||
vnode = cloneVNode(vnode as VNode)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return vnode
|
|
||||||
}
|
|
||||||
|
|
||||||
export function shouldUpdateComponent(
|
|
||||||
prevVNode: VNode,
|
|
||||||
nextVNode: VNode
|
|
||||||
): boolean {
|
|
||||||
const { data: prevProps, childFlags: prevChildFlags } = prevVNode
|
|
||||||
const { data: nextProps, childFlags: nextChildFlags } = nextVNode
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
// DEV only
|
|
||||||
export function getReasonForComponentUpdate(
|
|
||||||
prevVNode: VNode,
|
|
||||||
nextVNode: VNode
|
|
||||||
): any {
|
|
||||||
const reasons = []
|
|
||||||
const { childFlags: prevChildFlags } = prevVNode
|
|
||||||
const { childFlags: nextChildFlags } = nextVNode
|
|
||||||
if (
|
|
||||||
prevChildFlags !== nextChildFlags ||
|
|
||||||
(nextChildFlags & ChildrenFlags.DYNAMIC_SLOTS) > 0
|
|
||||||
) {
|
|
||||||
reasons.push({
|
|
||||||
type: `slots may have changed`,
|
|
||||||
tip: `use function slots + $stable: true to avoid slot-triggered child updates.`
|
|
||||||
})
|
|
||||||
}
|
|
||||||
const prevProps = prevVNode.data || EMPTY_OBJ
|
|
||||||
const nextProps = nextVNode.data || EMPTY_OBJ
|
|
||||||
for (const key in nextProps) {
|
|
||||||
if (nextProps[key] !== prevProps[key]) {
|
|
||||||
reasons.push({
|
|
||||||
type: 'prop changed',
|
|
||||||
key,
|
|
||||||
value: nextProps[key],
|
|
||||||
oldValue: prevProps[key]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (const key in prevProps) {
|
|
||||||
if (!(key in nextProps)) {
|
|
||||||
reasons.push({
|
|
||||||
type: 'prop changed',
|
|
||||||
key,
|
|
||||||
value: undefined,
|
|
||||||
oldValue: prevProps[key]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
type: 'triggered by parent',
|
|
||||||
reasons
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createComponentClassFromOptions(
|
|
||||||
options: ComponentOptions
|
|
||||||
): ComponentClass {
|
|
||||||
class AnonymousComponent extends Component {
|
|
||||||
static options = options
|
|
||||||
// indicate this component was created from options
|
|
||||||
static fromOptions = true
|
|
||||||
}
|
|
||||||
const proto = AnonymousComponent.prototype as any
|
|
||||||
for (const key in options) {
|
|
||||||
const value = options[key]
|
|
||||||
if (key === 'render') {
|
|
||||||
if (__COMPAT__) {
|
|
||||||
options.render = function() {
|
|
||||||
return value.call(this, h)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// so that we can call instance.render directly
|
|
||||||
proto.render = options.render
|
|
||||||
} else if (key === 'computed') {
|
|
||||||
// create computed setters on prototype
|
|
||||||
// (getters are handled by the render proxy)
|
|
||||||
for (const computedKey in value) {
|
|
||||||
const computed = value[computedKey]
|
|
||||||
const set = isObject(computed) && computed.set
|
|
||||||
if (set) {
|
|
||||||
Object.defineProperty(proto, computedKey, {
|
|
||||||
configurable: true,
|
|
||||||
set
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (key === 'methods') {
|
|
||||||
for (const method in value) {
|
|
||||||
if (__DEV__ && proto.hasOwnProperty(method)) {
|
|
||||||
warn(
|
|
||||||
`Object syntax contains method name that conflicts with ` +
|
|
||||||
`lifecycle hook: "${method}"`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
proto[method] = value[method]
|
|
||||||
}
|
|
||||||
} else if (__COMPAT__) {
|
|
||||||
if (key === 'name') {
|
|
||||||
options.displayName = value
|
|
||||||
} else if (key === 'render') {
|
|
||||||
options.render = function() {
|
|
||||||
return value.call(this, h)
|
|
||||||
}
|
|
||||||
} else if (key === 'beforeDestroy') {
|
|
||||||
options.beforeUnmount = value
|
|
||||||
} else if (key === 'destroyed') {
|
|
||||||
options.unmounted = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return AnonymousComponent as ComponentClass
|
|
||||||
}
|
|
@ -24,14 +24,16 @@ import {
|
|||||||
VNodeChildren
|
VNodeChildren
|
||||||
} from './vdom'
|
} from './vdom'
|
||||||
import { ComponentInstance } from './component'
|
import { ComponentInstance } from './component'
|
||||||
|
import {
|
||||||
|
createComponentInstance,
|
||||||
|
teardownComponentInstance
|
||||||
|
} from './componentInstance'
|
||||||
import {
|
import {
|
||||||
renderInstanceRoot,
|
renderInstanceRoot,
|
||||||
renderFunctionalRoot,
|
renderFunctionalRoot,
|
||||||
createComponentInstance,
|
|
||||||
teardownComponentInstance,
|
|
||||||
shouldUpdateComponent,
|
shouldUpdateComponent,
|
||||||
getReasonForComponentUpdate
|
getReasonForComponentUpdate
|
||||||
} from './componentUtils'
|
} from './componentRenderUtils'
|
||||||
import { KeepAliveSymbol } from './optional/keepAlive'
|
import { KeepAliveSymbol } from './optional/keepAlive'
|
||||||
import { pushWarningContext, popWarningContext, warn } from './warning'
|
import { pushWarningContext, popWarningContext, warn } from './warning'
|
||||||
import { resolveProps } from './componentProps'
|
import { resolveProps } from './componentProps'
|
||||||
|
@ -52,7 +52,4 @@ export { VNode, Slots } from './vdom'
|
|||||||
|
|
||||||
// Internal API, for libraries or renderers that need to perform low level work
|
// Internal API, for libraries or renderers that need to perform low level work
|
||||||
export * from './componentOptions'
|
export * from './componentOptions'
|
||||||
export {
|
export { createComponentInstance } from './componentInstance'
|
||||||
createComponentInstance,
|
|
||||||
createComponentClassFromOptions
|
|
||||||
} from './componentUtils'
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Component } from '../component'
|
import { Component } from '../component'
|
||||||
import { createComponentClassFromOptions } from '../componentUtils'
|
import { createComponentClassFromOptions } from '../componentOptions'
|
||||||
import {
|
import {
|
||||||
ComponentOptions,
|
ComponentOptions,
|
||||||
resolveComponentOptionsFromClass,
|
resolveComponentOptionsFromClass,
|
||||||
|
@ -4,7 +4,7 @@ import {
|
|||||||
FunctionalComponent
|
FunctionalComponent
|
||||||
} from './component'
|
} from './component'
|
||||||
import { VNodeFlags, ChildrenFlags } from './flags'
|
import { VNodeFlags, ChildrenFlags } from './flags'
|
||||||
import { createComponentClassFromOptions } from './componentUtils'
|
import { createComponentClassFromOptions } from './componentOptions'
|
||||||
import { EMPTY_OBJ, isObject, isArray, isFunction, isString } from '@vue/shared'
|
import { EMPTY_OBJ, isObject, isArray, isFunction, isString } from '@vue/shared'
|
||||||
import { RawChildrenType, RawSlots } from './h'
|
import { RawChildrenType, RawSlots } from './h'
|
||||||
import { FunctionalHandle } from './createRenderer'
|
import { FunctionalHandle } from './createRenderer'
|
||||||
|
Loading…
x
Reference in New Issue
Block a user