构建本身的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...

相关文章
相关标签/搜索