翻译自这里: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。
下一节: