大家好,我是哈默。今天我们来学习一下 vue3 中是如何进行渲染的。
基本使用
当我们要渲染一块 DOM 的时候,Vue 内部会将这块 DOM 的 vnode,传给 render 函数,完成渲染:
js
const vnode = { ... } // 某块 DOM 对应的 vnode
render(vnode, document.getElementById('app'))
render 函数
render 函数的定义是这样子的:
js
const render = (vnode, container) => {
if (vnode == null) {
// 卸载
if (container._vnode) {
unmount(container._vnode, null, null, true);
}
} else {
// 挂载或更新
patch(container._vnode || null, vnode, container, null, null, null, isSVG);
}
container._vnode = vnode;
};
我们可以通过 render 函数进行挂载、更新和删除。
另外需要注意的是,我们会把当前 vnode 作为 container._vnode
,这是为了再后面去 patch
的时候,作为 oldVNode
。
渲染:
js
render(vnode, document.getElementById("app"));
更新:
js
// 首次渲染
render(oldVNode, document.getElementById("app"));
// 更新
render(newVNode, document.getElementById("app"));
删除
js
// 首次渲染
render(oldVNode, document.getElementById("app"));
// 删除
render(null, document.getElementById("app"));
patch 函数
可以看到,当我们给 render 传递的第一个参数不是 null
的时候,我们会调用 patch 函数。
patch 函数:
js
const patch: PatchFn = (
oldVNode,
newVNode
) => {
if (oldVNode === newVNode) {
return
}
const { type, ref, shapeFlag } = newVNode
switch (type) {
case Text:
processText(oldVNode, newVNode)
break
case Comment:
processCommentNode(oldVNode, newVNode)
break
case Static:
if (oldVNode == null) {
mountStaticNode(newVNode)
} else if (__DEV__) {
patchStaticNode(oldVNode, newVNode)
}
break
case Fragment:
processFragment(
oldVNode,
newVNode
)
break
default:
if (shapeFlag & ShapeFlags.ELEMENT) {
processElement(
oldVNode,
newVNode
)
} else if (shapeFlag & ShapeFlags.COMPONENT) {
processComponent(
oldVNode,
newVNode
)
} else if (shapeFlag & ShapeFlags.TELEPORT) {
;(type as typeof TeleportImpl).process(
oldVNode as TeleportVNode,
newVNode as TeleportVNode
)
} else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
;(type as typeof SuspenseImpl).process(
oldVNode,
newVNode
)
} else if (__DEV__) {
warn('Invalid VNode type:', type, `(${typeof type})`)
}
}
}
在 patch 函数中,我们会根据 VNode 的不同 type 来进行不同的处理,比如处理文本节点、注释节点、DOM 节点、组件。。。
这里比如我们挑最常见的 DOM 节点来看,它会执行 processElement
函数。
processElement 函数:
js
const processElement = (oldVNode, newVNode) => {
if (oldVNode == null) {
mountElement(newVNode);
} else {
patchElement(oldVNode, newVNode);
}
};
如果 oldVNode 为 null,那么就是挂载。
如果 oldVNode 不为 null,那么就是更新。
那么对于挂载和更新来说,它就会涉及到新增、删除、移动等等的节点操作,还有属性挂载等逻辑。
多宿主环境的支持
Vue 渲染主要是发生在运行时(runtime)。
并且,Vue 不仅可以在浏览器端渲染(CSR),还可以在服务端渲染(SSR),所以 Vue 就将 runtime 分成了两个包,渲染的核心逻辑代码会放到 runtime-core 中;浏览器渲染相关的代码会放到 runtime-dom 中,最终通过 options 传入。
总结
Vue 的渲染主要就是通过 render 来进行的,核心的逻辑就在 runtime-core 中。