这个故事是我们一步一步构建自己版本的React的系列文章的一部分:
在我们开始之前,让我们回顾一下我们将使用的DOM API:
// Get an element by id
const domRoot = document.getElementById("root");
// Create a new element given a tag name
const domInput = document.createElement("input");
// Set properties
domInput["type"] = "text";
domInput["value"] = "Hi world";
domInput["className"] = "my-class";
// Listen to events
domInput.addEventListener("change", e => alert(e.target.value));
// Create a text node
const domText = document.createTextNode("");
// Set text node content
domText["nodeValue"] = "Foo";
// Append an element
domRoot.appendChild(domInput);
// Append a text node (same as previous)
domRoot.appendChild(domText);
请注意,我们正在设置元素属性而不是属性。这意味着只允许有效的属性。
我们将使用普通的JS对象来描述需要渲染的东西。我们将它们称为Didact Elements
。
这些元素有两个必需的属性:type
和props
。
-
type
可以是一个**{字符串string}或一个{函数function}**, 但我们将只使用-字符串-,直到我们在稍后的帖子中引入-组件-。 -
props
是可以为空的对象(但不为空)。props
可能有一个children
属性,它应该是一个Didact元素
的数组。
我们会很多地使用
Didact Elements
,所以从现在开始我们只会称它们为**{元素element}**, 不要与HTML element
混淆.
例如,像这样的一个元素:
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-元素
与React-元素
非常相似。
但是通常你在使用React
时不会创建React-元素
作为JS对象,
你可能使用JSX
或者甚至是createElement
。
我们将在Didact
中做同样的事情,但我们将会在系列下一篇文章中描述-createElement
-的代码。
下一步是将元素及其子元素呈现给dom。
我们将使用一个render
函数(相当于ReactDOM.render
)接收一个元素和一个dom容器
。
该函数应该创建由element
定义的dom子树
并将其附加到容器
中:
function render(element, parentDom) {
const { type, props } = element; // 获取类型 和 属性对象
const dom = document.createElement(type); // 创建-类型-element
const childElements = props.children || []; // 获取-孩子
childElements.forEach(childElement => render(childElement, dom)); // 每个孩子 都要加入-爸爸妈妈-的怀抱
//
parentDom.appendChild(dom); // 爸爸妈妈加入爷爷奶奶的怀抱
}
我们仍然缺少属性
和事件监听器
。让我们props
用Object.keys
函数迭代
属性名称并相应地-设置-它们:
function render(element, parentDom) {
const { type, props } = element;
const dom = document.createElement(type);
const isListener = name => name.startsWith("on");
// 是否开头-on
Object.keys(props).filter(isListener).forEach(name => {
const eventType = name.toLowerCase().substring(2); // 取两位后
dom.addEventListener(eventType, props[name]);
});
// 每一个开头-on 的属性-对应-函数 props[name] - >用-dom-addEvent 接连
const isAttribute = name => !isListener(name) && name != "children";
// 不是-监听事件 和 不能是-孩子
Object.keys(props).filter(isAttribute).forEach(name => {
dom[name] = props[name];
});
// 过滤出来的属性 - 赋予 - > dom
const childElements = props.children || [];
childElements.forEach(childElement => render(childElement, dom));
parentDom.appendChild(dom);
}
render
函数不支持的一件事是文本节点
。首先,我们需要定义文本元素的外观。例如,<span>Foo</span>
在React
中描述的元素如下所示:
const reactElement = {
type: "span",
props: {
children: ["Foo"] // 是孩子, 但也只是一个字符串
}
};
请注意,children
,只是一个字符串 ,而不是另一个元素对象。
这违背了我们如何定义Didact元素
:children
应该是元素的数组和所有元素应该有type
和props
。
如果我们遵循这些规则,我们将来会少一些if
判断。
因此,Didact Text Elements
将type==“TEXT ELEMENT”
相等,实际文本将位于nodeValue
属性中。
像这个:
const textElement = {
type: "span",
props: {
children: [
{
type: "TEXT ELEMENT", // 1
props: { nodeValue: "Foo" } // 2
}
]
}
};
现在我们已经规范了文本元素的数据结构,我们需要可以呈现它, 以便与其他元素一样,而区别也就是{type: "TEXT ELEMENT"
}。
我们应该使用createTextNode
,而不是使用createElement
。
就是这样,nodeValue
将会像其他属性一样设置。
function render(element, parentDom) {
const { type, props } = element;
// Create DOM element
const isTextElement = type === "TEXT ELEMENT"; // 文本类型判定
const dom = isTextElement
? document.createTextNode("")
: document.createElement(type);
// Add event listeners
const isListener = name => name.startsWith("on");
Object.keys(props).filter(isListener).forEach(name => {
const eventType = name.toLowerCase().substring(2);
dom.addEventListener(eventType, props[name]);
});
// Set properties
const isAttribute = name => !isListener(name) && name != "children";
Object.keys(props).filter(isAttribute).forEach(name => {
dom[name] = props[name];
});
// Render children
const childElements = props.children || [];
childElements.forEach(childElement => render(childElement, dom));
// Append to parent
parentDom.appendChild(dom);
}
我们创建了一个render函数
,允许我们将一个元素{element}及其子元素{children}
呈现给-DOM「parentDom.appendChild(dom);
」。
接下来我们需要的是createElement
的简单方法。
我们将在下一篇文章中做到这一点,在那里我们将让JSX与Didact
一起工作。
如果您想尝试我们迄今为止编写的代码,请检查codepen。你也可以从github回购中检查这个差异。
下一篇文章:Didact: Element creation and JSX {en} |-|_|🌟|Didact:元素创建和JSX {zh}