构建自己的React:(1)Rendering DOM elements
翻译自这里:https://engineering.hexacta.c...
本系列的目的是创建类似于React的一个简易的工具库。
DOM review
开始之前,我们先看下我们要用到的DOM API:
// 通过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才能被设置。
Didact Elements
我们用原生的JS对象来描述我们要渲染的东西,并称这类对象为Didact Elements。这些元素对应的JS对象都有两个必要的属性:type
和props
。type
可以是个字符串也可以是一个函数,但在我们介绍组件之前我们先只使用字符串。props
是一个可以为空(null
)的对象。props
下还可以含有children
属性,children
属性值为一个装有Didact Elements的数组。
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:
<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中我们也会使用相同的方法创建元素,但会将这部分内容放在下一节。
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>
这样的元素需要这样描述:
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); }
Summary
我们目前创建了一个可以渲染元素及其子元素为DOM的render
方法。下一步我们需要一个快速简单的方法来创建元素。下一节我们将在Didact中使用JSX。
下一节:
相关推荐
Vue和React是数据驱动视图,如何有效控制DOM操作?能不能把计算,更多的转移为js计算?因为js执行速度很快。patch函数-->patch,对比tag,对比tag与key,对比children