vue3渲染模块的domapi实现原理
vue3中渲染模块有两个子模块,分别是runtime-dom和runtime-core,其中,runtime-dom模块提供了常用的节点操作api和属性操作的api,而runtime-core中则包含虚拟dom的创建,diff算法等。
通过vue3中的runtime-core可以实现自己的渲染逻辑
本节来完善runtime-dom中的domapi,以便后面供runtime-core模块使用
vue中runtime-core的渲染逻辑
<script src="./runtime-dom.global.js"></script>
<div id="app"></div>
<script>
const {createRenderer,h } = VueRuntimeDOM
const {render,createApp} = createRenderer({
})
console.log(h('h1','hello world'),app)
</script>
- 结果输出虚拟dom h1节点
- h函数为runtime-core中创建虚拟dom的函数
- createRenderer函数可以让用户自己创建一个渲染器,从而创建元素
- createRenderer函数里面,需要用户传入如何操作节点的命令,即元素创建api和属性创建api
runtime-dom中相关api的实现
- 在runtime-dom新建nodeOps模块和patchProp模块,分别处理dom操作和属性操作
- dom 操作
- nodeOps模块为一个类,包含各种元素操作方法,主要有:
- 元素创建
- 文本节点创建
- 元素插入
- 移除节点
- 获取节点
- 获取节点父节点
- 获取兄弟节点
- 给文本节点设置内容
- 给元素节点设置内容
export const nodeOps = {
createElement(tagName){
return document.createElement(tagName)
},
createTextNode(text){
return document.createTextNode(text)
},
insert(element,container,anchor = null){
container.insertBefore(element,anchor);
},
remove(child){
const parent = child.parentNode;
if(parent){
parent.removeChild(child);
}
},
querySelector(selectors){
return document.querySelector(selectors);
},
parentNode(child){
return child.parentNode
},
nextSibling(child){
return child.nextSibling
},
setText(element,text){
element.nodeValue = text;
},
setElementText(element,text){
element.textContent = text;
}
}
- 属性操作
- 对节点的属性操作包含多种情况,取决于节点的属性是什么,总体来说可以分为四种:
- patchProp方法需要接收四个参数,分别是el:元素,key:属性名,preValue:之前的属性值,nextValue:新的属性值
- 通过key判断属于哪一种情况,分别进行相关的处理
- key === class:
- key === ‘style’:
- 若key为style,直接用nextValue进行替换对效率不太好,最好的方法是将nextValue和preValue进行对比,若之后存在的则保留,不存在的则删除。
export function patchStyle(el,preValue,nextValue){
if(preValue == null) preValue = {};
if(nextValue == null ) nextValue = {}
const style = el.style
for(let key in nextValue){
style[key] = nextValue[key]
}
if(preValue){
for(let key in preValue){
if(nextValue[key] == null){
style[key] = null;
}
}
}
}
- key为事件
- 若为事件,则需要将之前的事件缓存起来,当有新事件时,则直接添加新事件并缓存,若事件名不变,事件函数繁盛改变,则需要从缓存中取出原事件,将事件的执行内容替换
function createInvoker(preValue) {
const invoker = (e) => { invoker.value(e) };
invoker.value = preValue;
return invoker
}
export function patchEvent(el, eventName, nextValue) {
const invokers = el._vei || (el._vei = {});
const exitingInvoker = invokers[eventName];
if (exitingInvoker && nextValue) {
exitingInvoker.value = nextValue;
} else {
const eName = eventName.slice(2).toLowerCase()
if (nextValue) {
const invoker = createInvoker(nextValue);
invokers[eventName] = invoker;
el.addEventListener(eName, invoker);
} else if (exitingInvoker) {
el.removeEventListener(eName, exitingInvoker);
invokers[eventName] = null;
}
}
}
- 其它属性的处理和class处理类似
- 渲染器的创建
- 除去自己创建一个渲染器外,根据domapi和属性操作api,vue内部会给用户提供一个创建好的渲染器, 渲染器函数为createRenderer,传入要操作的domapi,即可返回一个渲染器函数render
const renderOptions = {patchProp,...nodeOps}
export function render(vnode,container){
let {render} = createRenderer(renderOptions);
return render(vnode,container)
}
export function render(vnode,container){
let {render} = createRenderer(renderOptions);
return render(vnode,container)
}
|