import config from '../config'
|
import Watcher, { WatcherOptions } from '../observer/watcher'
|
import { mark, measure } from '../util/perf'
|
import VNode, { createEmptyVNode } from '../vdom/vnode'
|
import { updateComponentListeners } from './events'
|
import { resolveSlots } from './render-helpers/resolve-slots'
|
import { toggleObserving } from '../observer/index'
|
import { pushTarget, popTarget } from '../observer/dep'
|
import type { Component } from 'types/component'
|
import type { MountedComponentVNode } from 'types/vnode'
|
|
import {
|
warn,
|
noop,
|
remove,
|
emptyObject,
|
validateProp,
|
invokeWithErrorHandling
|
} from '../util/index'
|
import { currentInstance, setCurrentInstance } from 'v3/currentInstance'
|
import { getCurrentScope } from 'v3/reactivity/effectScope'
|
import { syncSetupProxy } from 'v3/apiSetup'
|
|
export let activeInstance: any = null
|
export let isUpdatingChildComponent: boolean = false
|
|
export function setActiveInstance(vm: Component) {
|
const prevActiveInstance = activeInstance
|
activeInstance = vm
|
return () => {
|
activeInstance = prevActiveInstance
|
}
|
}
|
|
export function initLifecycle(vm: Component) {
|
const options = vm.$options
|
|
// locate first non-abstract parent
|
let parent = options.parent
|
if (parent && !options.abstract) {
|
while (parent.$options.abstract && parent.$parent) {
|
parent = parent.$parent
|
}
|
parent.$children.push(vm)
|
}
|
|
vm.$parent = parent
|
vm.$root = parent ? parent.$root : vm
|
|
vm.$children = []
|
vm.$refs = {}
|
|
vm._provided = parent ? parent._provided : Object.create(null)
|
vm._watcher = null
|
vm._inactive = null
|
vm._directInactive = false
|
vm._isMounted = false
|
vm._isDestroyed = false
|
vm._isBeingDestroyed = false
|
}
|
|
export function lifecycleMixin(Vue: typeof Component) {
|
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
|
const vm: Component = this
|
const prevEl = vm.$el
|
const prevVnode = vm._vnode
|
const restoreActiveInstance = setActiveInstance(vm)
|
vm._vnode = vnode
|
// Vue.prototype.__patch__ is injected in entry points
|
// based on the rendering backend used.
|
if (!prevVnode) {
|
// initial render
|
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
|
} else {
|
// updates
|
vm.$el = vm.__patch__(prevVnode, vnode)
|
}
|
restoreActiveInstance()
|
// update __vue__ reference
|
if (prevEl) {
|
prevEl.__vue__ = null
|
}
|
if (vm.$el) {
|
vm.$el.__vue__ = vm
|
}
|
// if parent is an HOC, update its $el as well
|
let wrapper: Component | undefined = vm
|
while (
|
wrapper &&
|
wrapper.$vnode &&
|
wrapper.$parent &&
|
wrapper.$vnode === wrapper.$parent._vnode
|
) {
|
wrapper.$parent.$el = wrapper.$el
|
wrapper = wrapper.$parent
|
}
|
// updated hook is called by the scheduler to ensure that children are
|
// updated in a parent's updated hook.
|
}
|
|
Vue.prototype.$forceUpdate = function () {
|
const vm: Component = this
|
if (vm._watcher) {
|
vm._watcher.update()
|
}
|
}
|
|
Vue.prototype.$destroy = function () {
|
const vm: Component = this
|
if (vm._isBeingDestroyed) {
|
return
|
}
|
callHook(vm, 'beforeDestroy')
|
vm._isBeingDestroyed = true
|
// remove self from parent
|
const parent = vm.$parent
|
if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
|
remove(parent.$children, vm)
|
}
|
// teardown scope. this includes both the render watcher and other
|
// watchers created
|
vm._scope.stop()
|
// remove reference from data ob
|
// frozen object may not have observer.
|
if (vm._data.__ob__) {
|
vm._data.__ob__.vmCount--
|
}
|
// call the last hook...
|
vm._isDestroyed = true
|
// invoke destroy hooks on current rendered tree
|
vm.__patch__(vm._vnode, null)
|
// fire destroyed hook
|
callHook(vm, 'destroyed')
|
// turn off all instance listeners.
|
vm.$off()
|
// remove __vue__ reference
|
if (vm.$el) {
|
vm.$el.__vue__ = null
|
}
|
// release circular reference (#6759)
|
if (vm.$vnode) {
|
vm.$vnode.parent = null
|
}
|
}
|
}
|
|
export function mountComponent(
|
vm: Component,
|
el: Element | null | undefined,
|
hydrating?: boolean
|
): Component {
|
vm.$el = el
|
if (!vm.$options.render) {
|
// @ts-expect-error invalid type
|
vm.$options.render = createEmptyVNode
|
if (__DEV__) {
|
/* istanbul ignore if */
|
if (
|
(vm.$options.template && vm.$options.template.charAt(0) !== '#') ||
|
vm.$options.el ||
|
el
|
) {
|
warn(
|
'You are using the runtime-only build of Vue where the template ' +
|
'compiler is not available. Either pre-compile the templates into ' +
|
'render functions, or use the compiler-included build.',
|
vm
|
)
|
} else {
|
warn(
|
'Failed to mount component: template or render function not defined.',
|
vm
|
)
|
}
|
}
|
}
|
callHook(vm, 'beforeMount')
|
|
let updateComponent
|
/* istanbul ignore if */
|
if (__DEV__ && config.performance && mark) {
|
updateComponent = () => {
|
const name = vm._name
|
const id = vm._uid
|
const startTag = `vue-perf-start:${id}`
|
const endTag = `vue-perf-end:${id}`
|
|
mark(startTag)
|
const vnode = vm._render()
|
mark(endTag)
|
measure(`vue ${name} render`, startTag, endTag)
|
|
mark(startTag)
|
vm._update(vnode, hydrating)
|
mark(endTag)
|
measure(`vue ${name} patch`, startTag, endTag)
|
}
|
} else {
|
updateComponent = () => {
|
vm._update(vm._render(), hydrating)
|
}
|
}
|
|
const watcherOptions: WatcherOptions = {
|
before() {
|
if (vm._isMounted && !vm._isDestroyed) {
|
callHook(vm, 'beforeUpdate')
|
}
|
}
|
}
|
|
if (__DEV__) {
|
watcherOptions.onTrack = e => callHook(vm, 'renderTracked', [e])
|
watcherOptions.onTrigger = e => callHook(vm, 'renderTriggered', [e])
|
}
|
|
// we set this to vm._watcher inside the watcher's constructor
|
// since the watcher's initial patch may call $forceUpdate (e.g. inside child
|
// component's mounted hook), which relies on vm._watcher being already defined
|
new Watcher(
|
vm,
|
updateComponent,
|
noop,
|
watcherOptions,
|
true /* isRenderWatcher */
|
)
|
hydrating = false
|
|
// flush buffer for flush: "pre" watchers queued in setup()
|
const preWatchers = vm._preWatchers
|
if (preWatchers) {
|
for (let i = 0; i < preWatchers.length; i++) {
|
preWatchers[i].run()
|
}
|
}
|
|
// manually mounted instance, call mounted on self
|
// mounted is called for render-created child components in its inserted hook
|
if (vm.$vnode == null) {
|
vm._isMounted = true
|
callHook(vm, 'mounted')
|
}
|
return vm
|
}
|
|
export function updateChildComponent(
|
vm: Component,
|
propsData: Record<string, any> | null | undefined,
|
listeners: Record<string, Function | Array<Function>> | undefined,
|
parentVnode: MountedComponentVNode,
|
renderChildren?: Array<VNode> | null
|
) {
|
if (__DEV__) {
|
isUpdatingChildComponent = true
|
}
|
|
// determine whether component has slot children
|
// we need to do this before overwriting $options._renderChildren.
|
|
// check if there are dynamic scopedSlots (hand-written or compiled but with
|
// dynamic slot names). Static scoped slots compiled from template has the
|
// "$stable" marker.
|
const newScopedSlots = parentVnode.data.scopedSlots
|
const oldScopedSlots = vm.$scopedSlots
|
const hasDynamicScopedSlot = !!(
|
(newScopedSlots && !newScopedSlots.$stable) ||
|
(oldScopedSlots !== emptyObject && !oldScopedSlots.$stable) ||
|
(newScopedSlots && vm.$scopedSlots.$key !== newScopedSlots.$key) ||
|
(!newScopedSlots && vm.$scopedSlots.$key)
|
)
|
|
// Any static slot children from the parent may have changed during parent's
|
// update. Dynamic scoped slots may also have changed. In such cases, a forced
|
// update is necessary to ensure correctness.
|
let needsForceUpdate = !!(
|
renderChildren || // has new static slots
|
vm.$options._renderChildren || // has old static slots
|
hasDynamicScopedSlot
|
)
|
|
const prevVNode = vm.$vnode
|
vm.$options._parentVnode = parentVnode
|
vm.$vnode = parentVnode // update vm's placeholder node without re-render
|
|
if (vm._vnode) {
|
// update child tree's parent
|
vm._vnode.parent = parentVnode
|
}
|
vm.$options._renderChildren = renderChildren
|
|
// update $attrs and $listeners hash
|
// these are also reactive so they may trigger child update if the child
|
// used them during render
|
const attrs = parentVnode.data.attrs || emptyObject
|
if (vm._attrsProxy) {
|
// force update if attrs are accessed and has changed since it may be
|
// passed to a child component.
|
if (
|
syncSetupProxy(
|
vm._attrsProxy,
|
attrs,
|
(prevVNode.data && prevVNode.data.attrs) || emptyObject,
|
vm,
|
'$attrs'
|
)
|
) {
|
needsForceUpdate = true
|
}
|
}
|
vm.$attrs = attrs
|
|
// update listeners
|
listeners = listeners || emptyObject
|
const prevListeners = vm.$options._parentListeners
|
if (vm._listenersProxy) {
|
syncSetupProxy(
|
vm._listenersProxy,
|
listeners,
|
prevListeners || emptyObject,
|
vm,
|
'$listeners'
|
)
|
}
|
vm.$listeners = vm.$options._parentListeners = listeners
|
updateComponentListeners(vm, listeners, prevListeners)
|
|
// update props
|
if (propsData && vm.$options.props) {
|
toggleObserving(false)
|
const props = vm._props
|
const propKeys = vm.$options._propKeys || []
|
for (let i = 0; i < propKeys.length; i++) {
|
const key = propKeys[i]
|
const propOptions: any = vm.$options.props // wtf flow?
|
props[key] = validateProp(key, propOptions, propsData, vm)
|
}
|
toggleObserving(true)
|
// keep a copy of raw propsData
|
vm.$options.propsData = propsData
|
}
|
|
// resolve slots + force update if has children
|
if (needsForceUpdate) {
|
vm.$slots = resolveSlots(renderChildren, parentVnode.context)
|
vm.$forceUpdate()
|
}
|
|
if (__DEV__) {
|
isUpdatingChildComponent = false
|
}
|
}
|
|
function isInInactiveTree(vm) {
|
while (vm && (vm = vm.$parent)) {
|
if (vm._inactive) return true
|
}
|
return false
|
}
|
|
export function activateChildComponent(vm: Component, direct?: boolean) {
|
if (direct) {
|
vm._directInactive = false
|
if (isInInactiveTree(vm)) {
|
return
|
}
|
} else if (vm._directInactive) {
|
return
|
}
|
if (vm._inactive || vm._inactive === null) {
|
vm._inactive = false
|
for (let i = 0; i < vm.$children.length; i++) {
|
activateChildComponent(vm.$children[i])
|
}
|
callHook(vm, 'activated')
|
}
|
}
|
|
export function deactivateChildComponent(vm: Component, direct?: boolean) {
|
if (direct) {
|
vm._directInactive = true
|
if (isInInactiveTree(vm)) {
|
return
|
}
|
}
|
if (!vm._inactive) {
|
vm._inactive = true
|
for (let i = 0; i < vm.$children.length; i++) {
|
deactivateChildComponent(vm.$children[i])
|
}
|
callHook(vm, 'deactivated')
|
}
|
}
|
|
export function callHook(
|
vm: Component,
|
hook: string,
|
args?: any[],
|
setContext = true
|
) {
|
// #7573 disable dep collection when invoking lifecycle hooks
|
pushTarget()
|
const prevInst = currentInstance
|
const prevScope = getCurrentScope()
|
setContext && setCurrentInstance(vm)
|
const handlers = vm.$options[hook]
|
const info = `${hook} hook`
|
if (handlers) {
|
for (let i = 0, j = handlers.length; i < j; i++) {
|
invokeWithErrorHandling(handlers[i], vm, args || null, vm, info)
|
}
|
}
|
if (vm._hasHookEvent) {
|
vm.$emit('hook:' + hook)
|
}
|
if (setContext) {
|
setCurrentInstance(prevInst)
|
prevScope && prevScope.on()
|
}
|
|
popTarget()
|
}
|