「哼,哼哼哼哼哼哼哼,哈哈,啊哈哈,啊哈哈哈哈哈哈哈哈哈!」——米哈遊
上回書說道……算了沒時間前情提要了。總之就先建個文件/src/runtime-dom/index.ts把前兩天我們整理的渲染dom跟給節點補屬性和事件的東西都拼裝到一起吧。
import { createRenderer } from "../runtime-core";
import { VNode } from "../runtime-core/vnode";
import { nodeOps } from "./nodeOps";
import { patchProp } from "./patchProp";
const renderOptions = Object.assign(nodeOps, { patchProp });
export type RenderOptions = typeof renderOptions;
export function render(vnode: VNode, container: VNode | HTMLElement) {
createRenderer(renderOptions).render(vnode, container);
}
除了createRenderer是我們接下來要去寫的東西,其他渲染dom元素相關的模塊都已經完成,並且我們在runtime-dom/index.ts把它們拼裝到了一起,之後就不需要再動runtime-dom裡的任何文件了。
實際上真正的vue源碼的renderOptions是會視情況變動的,所以以參數的形式傳入createRenderer。但還記得咱這次鐵人賽的標題吧?不聊太深,就淺談,所以renderOptions寫死,接下來要做的createRenderer會基於寫死的renderOptions去回傳一個render方法,比對接收的虛擬dom,並將之渲染成真實dom。
所以在/src/runtime-core/路徑下建立index.ts及renderer.ts兩個文件吧。
index.ts的內容極其簡單:
export * from './vnode';
export { createRenderer } from './renderer';
renderer.ts這邊,要先暴露一個createRenderer方法,基於runtime-dom的renderOptions渲染dom:
import { RenderOptions } from "../runtime-dom";
import { Text, VNode } from "./vnode";
export const createRenderer = (renderOptions: RenderOptions) => {
const {
insert: hostInsert,
remove: hostRemove,
setElementText: hostSetElementText,
setText: hostSetText,
parentNode: hostParentNode,
nextSibling: hostNextSibling,
createElement: hostCreateElement,
createText: hostCreateText,
patchProp: hostPatchProp
} = renderOptions;
const render = (vnode: VNode, container: any) => {
};
return { render };
};
然後是render的部分。render接收兩個參數,分別是代表新的虛擬dom的vnode,以及container,代表要把這些vnode渲染到哪個dom元素之中。
接著我們完成render的方法體:
const render = (vnode: VNode, container: any) => {
if (vnode == null) {
if (container._vnode) unmount(container._vnode);
} else {
patch(container._vnode || null, vnode, container);
}
container._vnode = vnode;
};
各位是否曾在vue的專案console.dir打印dom元素時看到過_vnode這個屬性?這個_vnode就是真實dom所對應的虛擬dom,我們可以在render方法的方法體最後一行中看到container._vnode = vnode,由此可以得知透過render方法直接渲染的dom元素身上會有一個_vnode屬性,指向它在內存中的虛擬dom。
因為有將虛擬dom的地址掛載到真實dom上,我們可以從container參數取得舊的虛擬dom,然後就能分成以下四種情況:
也就是如果新的虛擬dom是null,代表要把這個container裡面的所有dom元素都刪除;如果有接收到新的虛擬dom,則比對新舊虛擬dom。
把dom元素都刪除的unmount簡單粗暴,就真的只是字面上的把dom元素都刪除:
const unmount = (vnode: VNode) => {
hostRemove(vnode.el)
}
至於比對新舊虛擬dom的patch就比較困難了,從明天起,我們將會一步一步去實現這個比對新舊虛擬dom的方法。
githubmain分支commit「[Day 13] runtime-core——render方法」