6、Vue3渲染原理
虚拟节点的实现
形状标识
通过组合可以描述虚拟节点的类型
export const enum ShapeFlags { // vue3提供的形状标识
ELEMENT = 1,
FUNCTIONAL_COMPONENT = 1 << 1,
STATEFUL_COMPONENT = 1 << 2,
TEXT_CHILDREN = 1 << 3,
ARRAY_CHILDREN = 1 << 4,
SLOTS_CHILDREN = 1 << 5,
TELEPORT = 1 << 6,
SUSPENSE = 1 << 7,
COMPONENT_SHOULD_KEEP_ALIVE = 1 << 8,
COMPONENT_KEPT_ALIVE = 1 << 9,
COMPONENT = ShapeFlags.STATEFUL_COMPONENT | ShapeFlags.FUNCTIONAL_COMPONENT,
}
createVNode 实现
export function isVNode(value: any) {
return value ? value.__v_isVNode === true : false
}
export const createVNode = (type, props, children = null) => {
const shapeFlag = isString(type) ? ShapeFlags.ELEMENT : 0
const vnode = {
__v_isVNode: true,
type,
props,
key: props && props["key"],
el: null,
children,
shapeFlag,
}
if (children) {
let type = 0
if (Array.isArray(children)) {
type = ShapeFlags.ARRAY_CHILDREN
} else {
children = String(children)
type = ShapeFlags.TEXT_CHILDREN
}
vnode.shapeFlag |= type
// 如果shapeFlag为9 说明元素中包含一个文本
// 如果shapeFlag为17 说明元素中有多个子节点
}
return vnode
}
createVNode 的写法比较死板,我们让他变的更灵活些
h 实现
export function h(type, propsOrChildren?, children?) {
const l = arguments.length
if (l === 2) {
// 只有属性,或者一个元素儿子的时候
if (isObject(propsOrChildren) && !Array.isArray(propsOrChildren)) {
if (isVNode(propsOrChildren)) {
// h('div',h('span'))
return createVNode(type, null, [propsOrChildren])
}
return createVNode(type, propsOrChildren) // h('div',{style:{color:'red'}});
} else {
// 传递儿子列表的情况
return createVNode(type, null, propsOrChildren) // h('div',null,[h('span'),h('span')])
}
} else {
if (l > 3) {
// 超过3个除了前两个都是儿子
children = Array.prototype.slice.call(arguments, 2)
} else if (l === 3 && isVNode(children)) {
children = [children] // 儿子是元素将其包装成 h('div',null,[h('span')])
}
return createVNode(type, propsOrChildren, children) // h('div',null,'jw')
}
}
// 注意子节点是:数组、文本、null
createRenderer 实现
render 方法就是采用 runtime-dom 中提供的方法将虚拟节点转化成对应平台的真实节点渲染到指定容器中。
export function createRenderer(options) {
const {
insert: hostInsert,
remove: hostRemove,
patchProp: hostPatchProp,
createElement: hostCreateElement,
createText: hostCreateText,
setText: hostSetText,
setElementText: hostSetElementText,
parentNode: hostParentNode,
nextSibling: hostNextSibling,
} = options
const patch = (n1, n2, container) => {
// 初始化和diff算法都在这里喲
}
const render = (vnode, container) => {
if (vnode == null) {
if (container._vnode) {
} // 卸载
} else {
patch(container._vnode || null, vnode, container) // 初始化和更新
}
container._vnode = vnode
}
return {
render,
}
}
创建真实 DOM
const mountChildren = (children, container) => {
for (let i = 0; i < children.length; i++) {
patch(null, children[i], container)
}
}
const mountElement = (vnode, container) => {
const { type, props, shapeFlag } = vnode
let el = (vnode.el = hostCreateElement(type)) // 创建真实元素,挂载到虚拟节点上
if (props) {
// 处理属性
for (const key in props) {
// 更新元素属性
hostPatchProp(el, key, null, props[key])
}
}
if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
// 文本
hostSetElementText(el, vnode.children)
} else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
// 多个儿子
mountChildren(vnode.children, el)
}
hostInsert(el, container) // 插入到容器中
}
const patch = (n1, n2, container) => {
// 初始化和diff算法都在这里喲
if (n1 == n2) {
return
}
if (n1 == null) {
// 初始化的情况
mountElement(n2, container)
} else {
// diff算法
}
}
卸载 DOM
createRenderer(renderOptions).render(null, document.getElementById("app"))
const unmount = (vnode) => {
hostRemove(vnode.el)
}
const render = (vnode, container) => {
if (vnode == null) {
if (container._vnode) {
// 卸载
unmount(container._vnode) // 找到对应的真实节点将其卸载
}
} else {
patch(container._vnode || null, vnode, container) // 初始化和更新
}
container._vnode = vnode
}
优化调用方法
export const render = (vnode, container) => {
createRenderer(renderOptions).render(vnode, container)
}
export * from "@vue/runtime-core"
这样在页面中可以直接调用
render
方法进行渲染啦~