「♪♪♪♪♪♪♪♪♪♪♪♪♪♪」——莫札特
上回書說道,我們定義了虛擬dom節點的資料結構,接下來我們就寫一個函數,將虛擬dom渲染成真實dom。
為此,我們需要封裝一些對dom元素操作的方法,先建立/src/runtime-dom/nodeOps.ts:
export const nodeOps = {
insert(child: HTMLElement | Text, parent: HTMLElement, anchor: HTMLElement | Text = null) {
parent.insertBefore(child, anchor)
},
remove(child: HTMLElement | Text) {
const parentNode = child.parentNode
if (parentNode) parentNode.removeChild(child)
},
setElementText(el: HTMLElement, text: string) {
el.textContent = text
},
setText(node: HTMLElement | Text, text: string) {
node.nodeValue = text
},
querySelector(selector: string) {
return document.querySelector(selector)
},
parentNode(node: HTMLElement) {
return node.parentNode
},
nextSibling(node: HTMLElement) {
return node.nextSibling
},
createElement(tagName: string) {
return document.createElement(tagName)
},
createText(text: string) {
return document.createTextNode(text)
}
}
能學習vue3源碼的小夥伴,大多應該都跟我一樣,很久沒碰dom元素了吧?就讓我們來挨個兒複習一下。
insert(child: HTMLElement | Text, parent: HTMLElement, anchor: HTMLElement | Text = null) {
parent.insertBefore(child, anchor);
}
HTMLElement的insertBefore方法可以往自己裡面插入一個新的dom元素節點,那個節點會被插入至anchor前面,如果anchor是null,則插入至最後面。因此insert方法就是往parent裡面,anchor的前方或最後面插入child的方法。
remove、setElementText、querySelector、parentNode、createElement都挺見明知意的,跳過不講。
setText(node: HTMLElement | Text, text: string) {
node.nodeValue = text;
}
nodeValue和textContent、innerText的共通點是都是取節點裡面的文字,差別在於nodeValue只取文字節點裡的文字。比方說如果我們有個div標籤:
<div id="node">text</div>
<script>
const node = document.querySelector('#node')
console.log(node.nodeValue) // null
console.log(node.childNodes[0].nodeValue) // text
</script>
我們可以看到只有node.childNodes[0]這個div裡的文字節點,能取到非null的nodeValue。
至於textContent和innerText的差別就比較小的,比方說如果你用css:text-transform:uppercase;把文字改變成大寫,innerText會回傳大寫,textContent會回傳被css影響前原本的內容,所以這裡統一用textContent代替innerText可能更合適一些。
nextSibling(node: HTMLElement) {
return node.nextSibling;
}
HTMLElement的nextSibling屬性回傳同層級的下一個dom元素。
createText(text: string) {
return document.createTextNode(text);
}
創建文本節點,就是上面提到有nodeValue的那個文本節點。
有了這些方法,我們之後便能針對接收到的虛擬dom去將真實dom的節點一一渲染出來。但能把節點渲染出來當然還不夠,我們還必須要能夠解析這些d虛擬dom上的class、style、甚至是@click等事件,因此我們還要新建以下檔案:
並在patchProp.ts寫下以下代碼:
import { patchAttr } from "./modules/attr";
import { patchClass } from "./modules/class";
import { patchEvent } from "./modules/event";
import { patchStyle } from "./modules/style";
export function patchProp(el: HTMLElement, key: string, prevValue: any, nextValue: any) {
if (key === 'class') patchClass()
else if (key === 'style') patchStyle()
else if (/^\@/.test(key)) patchEvent()
else patchAttr()
}
從目前的代碼便可看出,這個patchProp會針對接收到的key,調用對應的方法,去給我們之後渲染完的dom元素加上class、style或綁定事件等等。明天我們將會去一一完成這些給渲染好的dom元素加上屬性或事件的方法,為之後渲染dom元素做準備。
githubmain分支commit「[Day 11] runtime-dom——封裝操作dom元素的方法 - 1」