Start the growth journey of Nuggets! This is the 16th day I have participated in the “Nuggets Daily New Project · February Update Challenge”, click to view the event details
Before reading this chapter, it is recommended to read [[Vue source code]keep-alive component logic analysis (middle)]([Vue source code]keep-alive component logic analysis (on)). This chapter mainly analyzes the secondary rendering logic of Abstract Component and keep-alive
Abstract component
The built-in components provided by Vue
all have an option to describe the type of the component. This option is {abstract: true }
, which indicates that the component is an abstract component. So what is an abstract component and why the distinction is made.
- Abstract components do not have real nodes, and will not be parsed and rendered into real
DOM
during rendering to welcome you, but only as an intermediate data transition layer, inkeep-alive
Medium is to process the component cache. - When the child component is initialized, the parent component instance will be mounted on the
parent
attribute of its own option, and theparent will be reversed during the
on theinitLifeCycle
processvnode
of the parent component, and add thevnode
of the child component to its$children
attribute, if you look up the parent in reverse In the process of components, if the parent component has the attributeabstract
, it can be judged that the component is an abstract component. At this time, use theparent
chain to continue searching until the component is not an abstract component. .
export function initLifecycle (vm: Component) {<!-- --> const options = vm. $options // locate first non-abstract parent let parent = options.parent if (parent & amp; & amp; !options. abstract) {<!-- --> // If the parent component has an abstract attribute, keep looking up until it is not an abstract component while (parent. $options. abstract & amp; & amp; parent. $parent) {<!-- --> parent = parent. $parent } parent.$children.push(vm) } vm.$parent = parent vm.$root = parent ? parent.$root : vm vm. $children = [] vm.$refs = {<!-- -->} vm._watcher = null vm._inactive = null vm._directInactive = false vm._isMounted = false vm._isDestroyed = false vm._isBeingDestroyed = false }
This connection established between parent and child components is the basis for communication between parent and child components
Re-render the component
The re-rendering process starts with data changes, and data changes in dynamic components will cause the process of relying on dispatch updates. When the data changes, the collected dependencies will be dispatched and updated.
Among them, the process responsible for instance mounting in the parent component will be executed as a dependency, that is, vm._update(vm._render(), hydrating)
of the parent component will be executed where _render
The function will generate a new virtual DOM
node for the component according to the data change, and _update
will eventually generate a real node for the new virtual DOM
node , and in the process of generating real nodes, the diff
algorithm will be used to compare the new and old virtual DOM
nodes, so as to change the real nodes as little as possible.
patch
is the process of comparing the new and old virtual DOM
, and patchVnode
is the core step, here we mainly focus on executing prePatch< on subcomponents /code> hook process
function patchVnode ( oldVnode, vnode, insertedVnodeQueue, ownerArray, index, removeOnly ) {<!-- --> //... if (isDef(data) & amp; & amp; isDef(i = data.hook) & amp; & amp; isDef(i = i.prepatch)) {<!-- --> i(oldVnode, vnode) } }
When executing the prePatch
hook, it will get the old and new component instances and execute the updateChildComponent
function, and updateChildComponent
will pair the old instance with the new component instance Update the state, including props
listeners
, etc., and finally call the global vm.$forceUpdate()
provided by vue
> method to re-render the instance.
const componentVNodeHooks = {<!-- --> prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {<!-- --> // instance of the new component const options = vnode. componentOptions // old component instance const child = vnode.componentInstance = oldVnode.componentInstance updateChildComponent( child, options.propsData, // updated props options. listeners, // updated listeners vnode, // new parent vnode options. children // new children ) }, } export function updateChildComponent ( vm: Component, propsData: ?Object, listeners: ?Object, parentVnode: MountedComponentVNode, renderChildren: ?Array<VNode> ) {<!-- --> // update old state // resolve slots + force update if has children if (needsForceUpdate) {<!-- --> vm.$slots = resolveSlots(renderChildren, parentVnode.context) // Force the instance to re-render vm.$forceUpdate() } if (process.env.NODE_ENV !== 'production') {<!-- --> isUpdatingChildComponent = false } }
Let's see what $forceUpdate
does first. $forceUpdate
is an api
exposed by vue
, which can make vue
Instance re-rendering is essentially to execute the dependencies collected by the instance. For dynamic components wrapped by keep-alive
, it is the vm that executes
processkeep-alive
._update(vm._render(), hydrating)
Vue.prototype.$forceUpdate = function () {<!-- --> var vm = this; if (vm._watcher) {<!-- --> vm._watcher.update(); } };
Reuse cache components
Since vm.$forceUpdate
will force the keep-alive
component to re-render, the keep-alive
component will execute render
again code> process, because the virtual DOM
is cached in the first rendering, so the cached components will be found from the cache
object when it is executed again.
// rendering function render () {<!-- --> // Get the value of the slot under keep-alive const slots = this. $slots. default // Get the first vnode node const vnode: VNode = getFirstComponentChild(slot) // get the first component instance const componentOptions: ?VNodeComponentOptions = vnode & amp; & amp; vnode.componentOptions if (componentOptions) {<!-- --> // check pattern // Get the name attribute of the first subcomponent vnode const name: ?string = getComponentName(componentOptions) const {<!-- --> include, exclude } = this if ( // Determine whether the subcomponent needs to be cached, and return the vnode object directly if no cache is required // not included (include & amp; & amp; (!name || !matches(include, name))) || //excluded (exclude & amp; & amp; name & amp; & amp; matches(exclude, name)) ) {<!-- --> return vnode } const {<!-- --> cache, keys } = this const key: ?string = vnode.key == null // same constructor may get registered as different local components // so cid alone is not enough (#3269) ?componentOptions.Ctor.cid + (componentOptions.tag ? `::${<!-- -->componentOptions.tag}` : '') : vnode.key if (cache[key]) {<!-- --> // Hit the cache, vnode.componentInstance = cache[key].componentInstance // make current key freshest // After deleting the key, add it again. The most recently used cache will be placed behind the array. This is the idea of the lru algorithm, which will be analyzed in detail later remove(keys, key) keys. push(key) } else {<!-- --> // first render, cache vnode cache[key] = vnode keys. push(key) // prune oldest entry if (this.max & amp; & amp; keys.length > parseInt(this.max)) {<!-- --> pruneCacheEntry(cache, keys[0], keys, this._vnode) } } // Add flags to the cache component vnode.data.keepAlive = true } return vnode || (slot & amp; & amp; slot[0]) }
When the render
function of keep-alive
is executed again, since the cache
object stores the virtual DOM reading event, it directly passes the cache [key]
Take out the cached component instance and assign it to the componentInstance
property of vnode
.
Replacement of real nodes
After executing the _render
process of the keep-alive
component, the next _update
process produces real nodes, since keep-alive
_update
process will call createComponent
to recursively create subcomponents. Love your vnode
, because it has already been rendered at the first time There is a cache, see what is the difference between rendering again and the first rendering
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {<!-- --> let i = vnode.data if (isDef(i)) {<!-- --> // isReactivated is used to determine whether there is a cache in the component. When rendering again, there is a componentInstance attribute in vnode and vnode.data.keepAlive is true, so isReactivated is true const isReactivated = isDef(vnode.componentInstance) & amp; & amp; i.keepAlive if (isDef(i = i.hook) & amp; & amp; isDef(i = i.init)) {<!-- --> // Internal hook to perform component initialization init i(vnode, false /* hydrating */) } if (isDef(vnode. componentInstance)) {<!-- --> // One of the functions is to keep the real DOM in the virtual DOM initComponent(vnode, insertedVnodeQueue) insert(parentElm, vnode.elm, refElm) if (isTrue(isReactivated)) {<!-- --> reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm) } return true } } }
In the createComponent
method, the initialization process of the component will still be executed, that is, the init
hook function of the component, but since this process already exists in the cache, the execution process is the same as the first Second initialization is not exactly the same
const componentVNodeHooks = {<!-- --> init (vnode: VNodeWithData, hydrating: boolean): ?boolean {<!-- --> if ( vnode.componentInstance & & !vnode.componentInstance._isDestroyed & & vnode.data.keepAlive ) {<!-- --> // When the cache already exists, execute the prePatch hook function // kept-alive components, treat as a patch const mountedNode: any = vnode // work around flow componentVNodeHooks. prepatch(mountedNode, mountedNode) } else {<!-- --> // Copy the component instance to the componentInstance property of the virtual DOM const child = vnode.componentInstance = createComponentInstanceForVnode( vnode, activeInstance ) // Mount after creating the component instance child.$mount(hydrating? vnode.elm : undefined, hydrating) } }, }
Obviously, because of the keepAlive
flag, the subcomponent is not going through the mounting process, but just executes the prePatch
hook to update the state of the component, making good use of the cache The real nodes kept between vnode
are replaced by nodes.