構建本身的React:(1)Rendering DOM elements

翻譯自這裏:https://engineering.hexacta.c...
本系列的目的是建立相似於React的一個簡易的工具庫。javascript

DOM review

開始以前,咱們先看下咱們要用到的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

Didact Elements

咱們用原生的JS對象來描述咱們要渲染的東西,並稱這類對象爲Didact Elements。這些元素對應的JS對象都有兩個必要的屬性:typepropstype能夠是個字符串也能夠是一個函數,但在咱們介紹組件以前咱們先只使用字符串。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

Render DOM Elements

下一步是將元素及其子元素渲染成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 DOM Text Nodes

目前render還不支持文本節點。首先咱們要定義文本節點是什麼樣的。在react中,一個<span>Foo</span>這樣的元素須要這樣描述:spa

const reactElement = {
  type: "span",
  props: {
    children: ["Foo"]
  }
}

注意到這裏的子元素已經不是對象,而只是一個字符串。這和咱們對Didact Elements的定義有不同:children應該是裝有Didact Elements的數組,而且全部元素都有typeprops屬性。若是咱們繼續遵照這個規則接下來咱們將較少使用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);
}

Summary

咱們目前建立了一個能夠渲染元素及其子元素爲DOM的render方法。下一步咱們須要一個快速簡單的方法來建立元素。下一節咱們將在Didact中使用JSX。

下一節:

https://engineering.hexacta.c...

相關文章
相關標籤/搜索