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 { 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 | null | undefined, listeners: Record> | undefined, parentVnode: MountedComponentVNode, renderChildren?: Array | 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 prev = currentInstance 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) } setContext && setCurrentInstance(prev) popTarget() }