Vue 源码
你想要的 vue 源码分析 · 09
patch patch 过程是深度遍历的过程,每次把正在激活的 vm 实例赋给 activeInstance,初始化子组件时把 activeInstance 作为一个参数传入:在 initLifecycle 拿到当前激活的 vm 实例,获取当前激活的 vm 的 parent,把当前激活 vm 实例 push 到 parent.$children中,vm.$parent 指向当前 vm 的父实例,i…
patch patch 过程是深度遍历的过程,每次把正在激活的 vm 实例赋给 activeInstance,初始化子组件时把 activeInstance 作为一个参数传入:在 initLifecycle 拿到当前激活的 vm 实例,获取当前激活的 vm 的 parent,把当前激活 vm 实例 push 到 parent.$children中,vm.$parent 指向当前 vm 的父实例,i…
patch 过程是深度遍历的过程,每次把正在激活的 vm 实例赋给 activeInstance,初始化子组件时把 activeInstance 作为一个参数传入:在 initLifecycle 拿到当前激活的 vm 实例,获取当前激活的 vm 的 parent,把当前激活 vm 实例 push 到 parent.$children中,vm.$parent 指向当前 vm 的父实例,initLifecycle 结束后,返回子组件的 vue 实例,再调用 child.$mount,最终会调用mountComponent,然后执行vm_render方法, 让vm.$vnode = _parentVnode,同时让渲染 vnode 的 parent,指向_parentVnode,return 渲染 vnode,然后执行_update,把渲染 vnode 赋给_vnode,_vnode 与$vnode是父子关系,_vnode是负责渲染的,$vnode 是一个占位符 vnode,当子组件执行 update 的时候,让当前 activeInstance 是 vm,这样子组件再创建一个子组件实例时,activeInstance 就可以作为一个父 vm 实例,然后再通过 initLifecycle 去建立父子组件关系,这样就实现了深度遍历,不断把当前 vue 实例赋给 activeInstance,同时通过 prevActiveInstance 去记录上一个,patch 完了,就可以恢复到上一个 activeInstance,这样就保证了 prevActiveInstance 和 activeInstance 是一个父子关系,在 update 的时候,会进行patch,传入当前 vnode,进行 createElm,让 parentElm 为空,子组件渲染,判断组件根 vnode 是不是一个组件,如果是组件,继续走 patch 深度遍历的逻辑,不是组件,对 children 的 data 进行判断,和普通节点渲染一样,继续遍历 children,执行 createEle 方法,如果 children 有子组件,依然会执行 createComponent,普通节点执行插入,因为此时的 parentElm 为空,parentElm 为空在执行 insert 的时候,不做任何 dom 插入操作,在调用 createComponent 时候,执行 init 方法,执行一系列的流程 patch,执行结束,执行 initComponent,把当前 vnode 的 el 返回出来,这时候 partnetElm 是有了,插入顺序是先子后父,子组件先 patch 后 insert,子组件再判断有没有组件
创建组件 VNode,如果创建了,直接 return
创建组件 VNode 如果 vnode 是组件 vnode,那么 i 就是 init 钩子函数,执行 init
通过 createComponentInstanceForVnode 创建一个 Vue 实例,从第五步回来调用$mount 方法来挂载子组件,会调用 mountComponent 方法,最后执行 vm_render 方法
继承于 Vue 的一个构造器 Sub,相当于 new Sub(options),最终执行_init
initInternalComponent 进行实例,组件自己接管了$mount 的过程,回到第三步
把之前传入的几个参数合并到内部的选项$options
当前组件 vnode 的 parent 指向_parentVnode,也就是 vm.$vnode,生成 vnode,执行 vm._update 去渲染 VNode
让 vm._vnode 和 vm.$vnode 变成一种父子关系 更新当前上下文的 Vue 实例
调用patch渲染 vnode,最后回到 createElm
如果组件的根节点是一个普通元素,那么 vm._vnode 也是普通元素,先创建一个父节点占位符,然后再遍历所有子 VNode 递归调用 createElm,在遍历过程中,如果遇见子 vnode 是另一个组件的 vnode,从第一步开始,递归构建整个组件树
完成 dom 插入,顺序是先子后父
patch 的过程会调用 createElm 创建元素及节点
1.createElm// src/core/vdom/patch.js
function createElm(vnode, insertedVnodeQueue, parentElm, refElm, nested, ownerArray, index) {
if (isDef(vnode.elm) && isDef(ownerArray)) {
// This vnode was used in a previous render!
// now it's used as a new node, overwriting its elm would cause
// potential patch errors down the road when it's used as an insertion
// reference node. Instead, we clone the node on-demand before creating
// associated DOM element for it.
vnode = ownerArray[index] = cloneVNode(vnode);
}
vnode.isRootInsert = !nested; // for transition enter check
// 为true时直接结束
if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
return;
}
// 在9.patch完后
// 这里面的vnode是组件渲染的vnode,是vm._vnode
// 如果组件的根节点是一个普通元素,那么vm._vnode也是普通元素
// 那么createComponent的返回值就是false,继续走下面了
// 根组件生成vnode一致,先弄一个占位符,遍历所有子vnode,递归调用creatElm,在遍历过程中
// 如果遇见vnode是一个组件的vnode,重复进行patch,递归的方式完整的构建了整个组件树
// 在完成整个组件的patch过程后,最后执行insert,如果patch过程中又创建了组件,
// 那么DOM的顺序是先父后子
const data = vnode.data;
const children = vnode.children;
const tag = vnode.tag;
if (isDef(tag)) {
if (process.env.NODE_ENV !== 'production') {
if (data && data.pre) {
creatingElmInVPre++;
}
if (isUnknownElement(vnode, creatingElmInVPre)) {
warn('Unknown custom element: <' + tag + '> - did you ' + 'register the component correctly? For recursive components, ' + 'make sure to provide the "name" option.', vnode.context);
}
}
vnode.elm = vnode.ns ? nodeOps.createElementNS(vnode.ns, tag) : nodeOps.createElement(tag, vnode);
setScope(vnode);
/* istanbul ignore if */
if (__WEEX__) {
// ...
} else {
createChildren(vnode, children, insertedVnodeQueue);
if (isDef(data)) {
invokeCreateHooks(vnode, insertedVnodeQueue);
}
insert(parentElm, vnode.elm, refElm);
}
if (process.env.NODE_ENV !== 'production' && data && data.pre) {
creatingElmInVPre--;
}
} else if (isTrue(vnode.isComment)) {
vnode.elm = nodeOps.createComment(vnode.text);
insert(parentElm, vnode.elm, refElm);
} else {
vnode.elm = nodeOps.createTextNode(vnode.text);
insert(parentElm, vnode.elm, refElm);
}
}
2.createComponent// src/core/vdom/patch.js
function createComponent(vnode, insertedVnodeQueue, parentElm, refElm) {
let i = vnode.data;
// 首先对vnode.data进行判断
if (isDef(i)) {
const isReactivated = isDef(vnode.componentInstance) && i.keepAlive;
// 如果vnode是组件vnode,那么i就是init钩子函数
if (isDef((i = i.hook)) && isDef((i = i.init))) {
i(vnode, false /* hydrating */);
}
if (isDef(vnode.componentInstance)) {
initComponent(vnode, insertedVnodeQueue);
insert(parentElm, vnode.elm, refElm);
if (isTrue(isReactivated)) {
reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm);
}
return true;
}
}
}
3.init// src/core/vdom/create-component.js
init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
if (
vnode.componentInstance &&
!vnode.componentInstance._isDestroyed &&
vnode.data.keepAlive
) {
// kept-alive components, treat as a patch
const mountedNode: any = vnode // work around flow
componentVNodeHooks.prepatch(mountedNode, mountedNode)
} else {
// 通过createComponentInstanceForVnode创建一个Vue实例,然后调用$mount方法来挂载子组件
const child = vnode.componentInstance = createComponentInstanceForVnode(
vnode,
activeInstance
)
// hydrating为true为服务端渲染,
// 相当于child.$mount(undefined, false)
// 最终会调用mountComponent 方法,最后执行vm_render方法`7.vm._render`
child.$mount(hydrating ? vnode.elm : undefined, hydrating)
}
}
4.createComponentInstanceForVnode// src/core/vdom/create-component.js
export function createComponentInstanceForVnode(
vnode: any, // we know it's MountedComponentVNode but flow doesn't
parent: any // activeInstance in lifecycle state
): Component {
// 先构建一个内部组件的参数
const options: InternalComponentOptions = {
// 代表是一个组件
_isComponent: true,
// 父vnode,占位符vnode,占位节点,当前激活的组件实例
_parentVnode: vnode,
// vm的一个实例
parent
};
// check inline-template render functions
const inlineTemplate = vnode.data.inlineTemplate;
if (isDef(inlineTemplate)) {
options.render = inlineTemplate.render;
options.staticRenderFns = inlineTemplate.staticRenderFns;
}
// 继承于 Vue 的一个构造器 Sub,相当于 new Sub(options)
// 子组件的实例化是在这个时候执行的
// 最终执行的是_init方法,Ctor是组件的一个构造器,实际上执行的是Sub构造函数,执行子组件的构造函数,执行组件的_init
return new vnode.componentOptions.Ctor(options);
}
5._init// src/core/instance/init.js
Vue.prototype._init = function(options?: Object) {
const vm: Component = this;
// a uid
vm._uid = uid++;
let startTag, endTag;
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
startTag = `vue-perf-start:${vm._uid}`;
endTag = `vue-perf-end:${vm._uid}`;
mark(startTag);
}
// a flag to avoid this being observed
vm._isVue = true;
// 合并option时_isComponent为true,所以走到initInternalComponent进行实例
if (options && options._isComponent) {
initInternalComponent(vm, options);
} else {
vm.$options = mergeOptions(resolveConstructorOptions(vm.constructor), options || {}, vm);
}
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
initProxy(vm);
} else {
vm._renderProxy = vm;
}
// expose real self
vm._self = vm;
initLifecycle(vm);
initEvents(vm);
initRender(vm);
callHook(vm, 'beforeCreate');
initInjections(vm); // resolve injections before data/props
initState(vm);
initProvide(vm); // resolve provide after data/props
callHook(vm, 'created');
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
vm._name = formatComponentName(vm, false);
mark(endTag);
measure(`vue ${vm._name} init`, startTag, endTag);
}
// 由于组件初始化的时候不传el,组件自己接管了$mount的过程,回到了`3.init`
if (vm.$options.el) {
vm.$mount(vm.$options.el);
}
};
6.initInternalComponent// src/core/instance/init.js
export function initInternalComponent(vm: Component, options: InternalComponentOptions) {
const opts = (vm.$options = Object.create(vm.constructor.options));
// doing this because it's faster than dynamic enumeration.
const parentVnode = options._parentVnode;
// 这两个赋值是把之前传入的几个参数合并到内部的选项$options
// 子组件的父vm实例
opts.parent = options.parent;
// parentVnode是占位符
opts._parentVnode = parentVnode;
const vnodeComponentOptions = parentVnode.componentOptions;
opts.propsData = vnodeComponentOptions.propsData;
opts._parentListeners = vnodeComponentOptions.listeners;
opts._renderChildren = vnodeComponentOptions.children;
opts._componentTag = vnodeComponentOptions.tag;
if (options.render) {
opts.render = options.render;
opts.staticRenderFns = options.staticRenderFns;
}
}
7.vm._render// src/core/instance/render.js
Vue.prototype._render = function(): VNode {
const vm: Component = this;
// _parentVnode就是当前组件的父vnode,而render函数生成的vnode是当前组件的渲染vnode
// vnode的parent指向_parentVnode,也就是vm.$vnode,他们是父子关系
const { render, _parentVnode } = vm.$options;
// reset _rendered flag on slots for duplicate slot check
if (process.env.NODE_ENV !== 'production') {
for (const key in vm.$slots) {
// $flow-disable-line
vm.$slots[key]._rendered = false;
}
}
if (_parentVnode) {
vm.$scopedSlots = _parentVnode.data.scopedSlots || emptyObject;
}
// set parent vnode. this allows render functions to have access
// to the data on the placeholder node.
vm.$vnode = _parentVnode;
// render self
let vnode;
try {
vnode = render.call(vm._renderProxy, vm.$createElement);
} catch (e) {
handleError(e, vm, `render`);
// return error render result,
// or previous vnode to prevent render error causing blank component
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
if (vm.$options.renderError) {
try {
vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e);
} catch (e) {
handleError(e, vm, `renderError`);
vnode = vm._vnode;
}
} else {
vnode = vm._vnode;
}
} else {
vnode = vm._vnode;
}
}
// return empty vnode in case the render function errored out
if (!(vnode instanceof VNode)) {
if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) {
warn('Multiple root nodes returned from render function. Render function ' + 'should return a single root node.', vm);
}
vnode = createEmptyVNode();
}
// set parent
vnode.parent = _parentVnode;
// 执行完vm_render生成vnode后,执行vm._update去渲染vnode `8.vm._update`
return vnode;
};
8.vm._update// src/core/instance/lifecycle.js
Vue.prototype._update = function(vnode: VNode, hydrating?: boolean) {
const vm: Component = this;
const prevEl = vm.$el;
const prevVnode = vm._vnode;
const prevActiveInstance = activeInstance;
// patch过程是深度遍历的过程,每次把正在激活的vm实例赋给activeInstance,初始化子组件时把activeInstance作为一个参数传入
// 在 vm._update 的过程中
// 把当前的 vm 赋值给 activeInstance
// prevActiveInstance 和当前的 vm 是一个父子关系
activeInstance = vm;
// vnode是vm._render渲染的vnode,
// vm._vnode 和 vm.$vnode 是一种父子关系
// vm._vnode.parent === vm.$vnode
vm._vnode = vnode;
// Vue.prototype.__patch__ is injected in entry points
// based on the rendering backend used.
if (!prevVnode) {
// 最后调用__patch__渲染vnode
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */);
} else {
// updates
vm.$el = vm.__patch__(prevVnode, vnode);
}
// activeInstance 是Vue实例
// 在 lifecycle 模块的全局变量
// 在文件顶部定义:export let activeInstance: any = null
// Vue 整个初始化是一个深度遍历的过程
// 在遍历的时候需要知道当前上下文是什么
// activeInstance作为遍历时父组件的实例
// 在实例的时候需要initInternalComponent合并option,把parent保存在vm.$options中,
// 在 $mount 之前会调用 initLifecycle(vm)方法(5._init做了)
activeInstance = prevActiveInstance;
// 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
if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
vm.$parent.$el = vm.$el;
}
// updated hook is called by the scheduler to ensure that children are
// updated in a parent's updated hook.
};
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;
}
// 当前的 vm 存储到父实例的 $children 中
// 8.vm._update
// 在 vm._update 的过程中
// 把当前的 vm 赋值给 activeInstance
// prevActiveInstance 和当前的 vm 是一个父子关系
// 当一个vm实例完成patch或者update,activeInstance 会回到它的父实例
// 保证了深度遍历过程中,实例化子组件的时候能传入当前子组件的父Vue,
// 并在_init过程中,通过 vm.$parent 把这个父子关系保留
parent.$children.push(vm);
}
// vm.$parent 就是用来保留当前 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;
}
9.patchreturn function patch(oldVnode, vnode, hydrating, removeOnly) {
if (isUndef(vnode)) {
if (isDef(oldVnode)) invokeDestroyHook(oldVnode);
return;
}
let isInitialPatch = false;
const insertedVnodeQueue = [];
if (isUndef(oldVnode)) {
// empty mount (likely as component), create new root element
isInitialPatch = true;
//
createElm(vnode, insertedVnodeQueue);
} else {
const isRealElement = isDef(oldVnode.nodeType);
if (!isRealElement && sameVnode(oldVnode, vnode)) {
// patch existing root node
patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly);
} else {
// ...
}
}
invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch);
return vnode.elm;
};
createComponent 返回 true->子组件初始化:init,vnode,lifecycle->子组件 render,生成子组件的 vnode->子组件 patch,遍历子组件渲染 vnode,发现还有子组件,递归调用 createComponent
为当前激活的 vm 实例,是深度遍历,vm 实例作为 parent 实例传入,是为了建立父子关系; vm.$vnode 为组件的占位 vnode,vm._vnode 为组件渲染的 vnode
先子后父,子组件往父组件插,父组件为父组件的父组件里面插入,最终插入到 body 中
暂无评论
确认操作
请再次确认编辑图片
待上传图片 (剩余 0 张)
项目详情
确认评论邮箱
邀请制评论