翻譯自這裏:https://engineering.hexacta.c...
本系列的目的是建立相似於React的一個簡易的工具庫。javascript
開始以前,咱們先看下咱們要用到的DOM API:java
// 經過id查找元素 const domRoot = document.getElementById("root"); // 根據執行標籤名建立元素 const domInput = document.createElement("input"); // 設置元素屬性 domInput["type"] = "text"; domInput["value"] = "Hi world"; domInput["className"] = "my-class"; // 添加事件監聽 domInput.addEventListener("change", e => alert(e.target.value)); // 建立文本節點 const domText = document.createTextNode(""); // 設置節點內容 domText["nodeValue"] = "Foo"; // 往頁面上添加元素 domRoot.appendChild(domInput); // 往頁面上添加文本節點 domRoot.appendChild(domText);
注意到這裏咱們給元素設置了properties而不是attributes,並且只有有效的properties才能被設置。node
咱們用原生的JS對象來描述咱們要渲染的東西,並稱這類對象爲Didact Elements。這些元素對應的JS對象都有兩個必要的屬性:type
和props
。type
能夠是個字符串也能夠是一個函數,但在咱們介紹組件以前咱們先只使用字符串。props
是一個能夠爲空(null
)的對象。props
下還能夠含有children
屬性,children
屬性值爲一個裝有Didact Elements的數組。react
咱們後面將會頻繁的使用Didact Elements,因此咱們會用元素稱呼Didact Elements。不要和HTML的元素搞混了,HTML元素會被稱做DOM元素或者使用命名變量時乾脆就叫
dom
。
舉個例子,咱們會用下面這個對象:數組
const element = { type: "div", props: { id: "container", children: [ { type: "input", props: { value: "foo", type: "text" } }, { type: "a", props: { href: "/bar" } }, { type: "span", props: {} } ] } };
來描述下面這個dom:app
<div id="container"> <input value="foo" type="text"> <a href="/bar"></a> <span></span> </div>
Didact Elements和React Elements很像。但一般狀況下你不會使用JS手動去建立一個React Elements,更多的是使用JSX或者是createElement
方法來建立。在Didact中咱們也會使用相同的方法建立元素,但會將這部份內容放在下一節。dom
下一步是將元素及其子元素渲染成dom。咱們使用render
(相似於ReactDOM.render
)方法來接收一個元素和一個dom容器。這個方法會將這個元素描述的dom結構建立出來,並添加到容器內。函數
function render(element, parentDom){ const { type, props } = element; const dom = document.createElement(type); const childElements = props.children || []; childElements.forEach(childElement => render(childElement, dom)); parentDom.appendChild(dom); }
咱們如今仍沒有處理屬性和事件。咱們先用Object.keys
來獲取props
中的屬性名字,而後循環將它們設定到元素上:工具
function render(element, parentDom){ const { type, props } = element; const dom = document.createElement(type); const isListener = name => name.startsWith("on"); Object.keys(props).filter(isListener).forEach(name => { const eventType = name.toLowerCase().substring(2); dom.addEventListener(eventType, props[name]); }) const isAttribute = name => !isListener(name) && name != "children"; Object.keys(props).filter(isAttribute).forEach(name => { dom[name] = props[name]; }) const childElements = props.children || []; childElements.forEach(childElement => render(childElement, dom)); parentDom.appendChild(dom); }
目前render
還不支持文本節點。首先咱們要定義文本節點是什麼樣的。在react中,一個<span>Foo</span>
這樣的元素須要這樣描述:spa
const reactElement = { type: "span", props: { children: ["Foo"] } }
注意到這裏的子元素已經不是對象,而只是一個字符串。這和咱們對Didact Elements的定義有不同:children
應該是裝有Didact Elements的數組,而且全部元素都有type
和props
屬性。若是咱們繼續遵照這個規則接下來咱們將較少使用if
的次數。因此,Didact Elements將會使用type="TEXT_ELEMENT"
來表示文本節點,並使用nodeValue
來裝文本值。例以下面這樣:
const textElement = { type: "span", props: { children: [ { type: "TEXT_ELEMENT", props: { nodeValue: "Foo" } } ] } };
如今咱們已經定義好了可以渲染的文本節點。和其餘節點不一樣的是,文本節點須要使用createTextNode
來建立而不是createElement
,而nodeValue
會經過相同方法來設置。
function render(element, parentDom) { const { type, props } = element; // 建立DOM const isTextElement = type === "TEXT_ELEMENT"; const dom = isTextElement ? document.createTextNode("") : document.createElement(type); // 添加事件監聽 const isListener = name => name.startsWith("on"); Object.keys(props).filter(isListener).forEach(name => { const eventType = name.toLowerCase().substring(2); dom.addEventListener(eventType, props[name]); }); // 設置屬性 const isAttribute = name => !isListener(name) && name != "children"; Object.keys(props).filter(isAttribute).forEach(name => { dom[name] = props[name]; }); // 遞歸渲染子元素 const childElements = props.children || []; childElements.forEach(childElement => render(childElement, dom)); // 將dom添加到父dom內 parentDom.appendChild(dom); }
咱們目前建立了一個能夠渲染元素及其子元素爲DOM的render
方法。下一步咱們須要一個快速簡單的方法來建立元素。下一節咱們將在Didact中使用JSX。
下一節: