Vue 源码
你想要的 vue 源码分析 · 06
vm._update() _update 方法的作用是把 VNode 渲染成真实的 DOM,调用时期是首次渲染和数据改变,数据改变驱动视图变化。 vm._ patch 调用了 方法的返回值,因为把 VNode 渲染到不同平台的方法是不同的,所以使用了 createPatchFunction 进行函数柯里化,把差异函数提前固化,不同每次调用 patch 的时候传入 nodeOps 和 modules…
vm._update() _update 方法的作用是把 VNode 渲染成真实的 DOM,调用时期是首次渲染和数据改变,数据改变驱动视图变化。 vm._ patch 调用了 方法的返回值,因为把 VNode 渲染到不同平台的方法是不同的,所以使用了 createPatchFunction 进行函数柯里化,把差异函数提前固化,不同每次调用 patch 的时候传入 nodeOps 和 modules…
_update 方法的作用是把 VNode 渲染成真实的 DOM,调用时期是首次渲染和数据改变,数据改变驱动视图变化。
// src/core/instance/lifecycle.js
Vue.prototype._update = function(vnode: VNode, hydrating?: boolean) {
const vm: Component = this;
// 下面3个是为了数据改变时调用的update
const prevEl = vm.$el;
const prevVnode = vm._vnode;
const prevActiveInstance = activeInstance;
activeInstance = vm;
vm._vnode = vnode;
// 核心就是调用vm.__patch__方法
if (!prevVnode) {
// 定义在\src\platforms\web\runtime\index.js Vue.prototype.__patch__ = inBrowser ? patch : noop
// 又在\src\platforms\web\runtime\index.js定义,export const patch: Function = createPatchFunction({ nodeOps, modules })
// createPatchFunction,定义了很多函数,返回patch,vm._patch调用的是patch
// 是为了函数柯里化,nodeOps在不同平台下是不同的,modules不同平台的是不同的
// 如果不这样做,每次执行patch的时候,都要判断在哪个平台
// 通过柯里化,磨平了平台差异化,每次调用patch的时候不能再次判断,通过提前判断差异化参数,在使用的时候不用再次判断
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */);
} else {
// updates
vm.$el = vm.__patch__(prevVnode, vnode);
}
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.
};
vm.__patch__vm._patch 调用了
createPatchFunction方法的返回值,因为把 VNode 渲染到不同平台的方法是不同的,所以使用了 createPatchFunction 进行函数柯里化,把差异函数提前固化,不同每次调用 patch 的时候传入 nodeOps 和 modules,nodeOps 是一些平台 DOM 的操作方法,modules 是平台的一些模块,在整个 patch 过程中,会执行一系列的钩子函数
// 相当于调用vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false),这个例子中,最里面是文本vnode,实际上是递归创建了一个完整的dom树,并插入到body中
var app = new Vue({
el: '#app',
render: function(createElement) {
return createElement(
'div',
{
attrs: {
id: 'app'
}
},
this.message
);
},
data: {
message: 'Hello Vue!'
}
});
patch// \src\core\vdom\patch.js
// 参数1:oldVnode 老的VNode的节点,
// 参数2:vnode执行_render后的VNode的节点
// 参数3:hydrating表示是否服务端渲染
// 参数4:removeOnly
return 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);
// 如果是真实的DOM元素,新老node相同,主要是在数据更新时调用
if (!isRealElement && sameVnode(oldVnode, vnode)) {
// patch existing root node
patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly);
} else {
// 如果是真实的DOM元素,在首次渲染时调用
if (isRealElement) {
// mounting to a real element
// check if this is server-rendered content and if we can perform
// a successful hydration.
if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {
oldVnode.removeAttribute(SSR_ATTR);
hydrating = true;
}
if (isTrue(hydrating)) {
if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {
invokeInsertHook(vnode, insertedVnodeQueue, true);
return oldVnode;
} else if (process.env.NODE_ENV !== 'production') {
warn('The client-side rendered virtual DOM tree is not matching ' + 'server-rendered content. This is likely caused by incorrect ' + 'HTML markup, for example nesting block-level elements inside ' + '<p>, or missing <tbody>. Bailing hydration and performing ' + 'full client-side render.');
}
}
// 把 oldVnode 转换成 VNode
oldVnode = emptyNodeAt(oldVnode);
}
// replacing existing element
const oldElm = oldVnode.elm;
const parentElm = nodeOps.parentNode(oldElm);
// 通过虚拟节点创建真实的 DOM 并插入到它的父节点
createElm(
vnode,
insertedVnodeQueue,
// extremely rare edge case: do not insert if old element is in a
// leaving transition. Only happens when combining transition +
// keep-alive + HOCs. (#4590)
oldElm._leaveCb ? null : parentElm,
nodeOps.nextSibling(oldElm)
);
// update parent placeholder node element, recursively
if (isDef(vnode.parent)) {
let ancestor = vnode.parent;
const patchable = isPatchable(vnode);
while (ancestor) {
for (let i = 0; i < cbs.destroy.length; ++i) {
cbs.destroy[i](ancestor);
}
ancestor.elm = vnode.elm;
if (patchable) {
for (let i = 0; i < cbs.create.length; ++i) {
cbs.create[i](emptyNode, ancestor);
}
// #6513
// invoke insert hooks that may have been merged by create hooks.
// e.g. for directives that uses the "inserted" hook.
const insert = ancestor.data.hook.insert;
if (insert.merged) {
// start at index 1 to avoid re-invoking component mounted hook
for (let i = 1; i < insert.fns.length; i++) {
insert.fns[i]();
}
}
} else {
registerRef(ancestor);
}
ancestor = ancestor.parent;
}
}
// 如果定义了parentElm,把之前的节点删掉
if (isDef(parentElm)) {
removeVnodes(parentElm, [oldVnode], 0, 0);
} else if (isDef(oldVnode.tag)) {
invokeDestroyHook(oldVnode);
}
}
}
invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch);
return vnode.elm;
};
createElm主要作用是通过虚拟节点创建真实的 DOM 并插入到它的父节点,如果 VNode 不包含 tag,会产生一个注释节点或者纯文本节点,可直接插入到父元素中,
// \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
// 尝试建一个组件节点,按照例子是false
if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
return;
}
const data = vnode.data;
const children = vnode.children;
const tag = vnode.tag;
// 判断VNode是否包含tag,文本是没有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__) {
// in Weex, the default insertion order is parent-first.
// List items can be optimized to use children-first insertion
// with append="tree".
const appendAsTree = isDef(data) && isTrue(data.appendAsTree);
if (!appendAsTree) {
if (isDef(data)) {
invokeCreateHooks(vnode, insertedVnodeQueue);
}
insert(parentElm, vnode.elm, refElm);
}
createChildren(vnode, children, insertedVnodeQueue);
if (appendAsTree) {
if (isDef(data)) {
invokeCreateHooks(vnode, insertedVnodeQueue);
}
insert(parentElm, vnode.elm, refElm);
}
} 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);
}
}
createChildren遍历子虚拟节点,递归调用 createElm,传入 vnode.elm 作为父容器的 DOM 节点占位符
// \src\core\vdom\patch.js
function createChildren(vnode, children, insertedVnodeQueue) {
if (Array.isArray(children)) {
if (process.env.NODE_ENV !== 'production') {
// 对key进行校验
checkDuplicateKeys(children);
}
// 遍历子虚拟节点,递归调用createElm,传入vnode.elm作为富容器的DOM节点占位符
for (let i = 0; i < children.length; ++i) {
createElm(children[i], insertedVnodeQueue, vnode.elm, null, true, children, i);
}
} else if (isPrimitive(vnode.text)) {
nodeOps.appendChild(vnode.elm, nodeOps.createTextNode(String(vnode.text)));
}
}
invokeCreateHooks执行所有的 create 的钩子并把 vnode push 到 insertedVnodeQueue
// \src\core\vdom\patch.js
function invokeCreateHooks(vnode, insertedVnodeQueue) {
for (let i = 0; i < cbs.create.length; ++i) {
cbs.create[i](emptyNode, vnode);
}
i = vnode.data.hook; // Reuse variable
if (isDef(i)) {
if (isDef(i.create)) i.create(emptyNode, vnode);
// 执行所有的 create 的钩子并把 vnode push 到 insertedVnodeQueue
if (isDef(i.insert)) insertedVnodeQueue.push(vnode);
}
}
insert把 DOM 插入到父节点中,先调用 createChildren,子元素会优先调用 insert,进行递归,所以整个 vnode 树节点的插入顺序是先子后父
// \src\core\vdom\patch.js
function insert(parent, elm, ref) {
if (isDef(parent)) {
if (isDef(ref)) {
// 有参考节点,判断参考节点和父节点是否相等
if (ref.parentNode === parent) {
// 把dom插入到父节点
nodeOps.insertBefore(parent, elm, ref);
}
} else {
nodeOps.appendChild(parent, elm);
}
}
}
暂无评论
确认操作
请再次确认编辑图片
待上传图片 (剩余 0 张)
项目详情
确认评论邮箱
邀请制评论