forked from chinanf-boy/didact-explain
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
dbe51ac
commit 4e09a2b
Showing
7 changed files
with
1,132 additions
and
1,061 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
node_modules/ | ||
.DS_Store | ||
.DS_Store | ||
.doc-templite.js |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,221 @@ | ||
|
||
|
||
## 1. 渲染DOM元素 | ||
|
||
> 这个故事是我们一步一步构建自己版本的React的系列文章的一部分: | ||
### 1.1 DOM审查 | ||
|
||
在我们开始之前,让我们回顾一下我们将使用的DOM API: | ||
|
||
``` js | ||
// 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); | ||
``` | ||
|
||
> [>>> codepen.io](https://codepen.io/pomber/pen/aWBLJR) | ||
请注意,我们正在设置[元素属性而不是属性](http://stackoverflow.com/questions/6003819/properties-and-attributes-in-html)。这意味着只允许有效的属性。 | ||
|
||
### 1.2 Didact元素 | ||
|
||
我们将使用普通的JS对象来描述需要渲染的东西。我们将它们称为`Didact Elements`。 | ||
|
||
这些元素有两个必需的属性:`type`和`props`。 | ||
|
||
- `type`可以是一个**{字符串string}**或一个**{函数function}**, 但我们将只使用-字符串-,直到我们在稍后的帖子中引入-组件-。 | ||
|
||
- `props`是可以为空的对象(但不为空)。`props`可能有一个`children`属性,它应该是一个`Didact元素`的数组。 | ||
|
||
> 我们会很多地使用`Didact Elements`,所以从现在开始我们只会称它们为**{元素element}**, 不要与`HTML element`混淆. | ||
例如,像这样的一个元素: | ||
|
||
``` js | ||
const element = { | ||
type: "div", | ||
props: { | ||
id: "container", | ||
children: [ | ||
{ type: "input", props: { value: "foo", type: "text" } }, | ||
{ type: "a", props: { href: "/bar" } }, | ||
{ type: "span", props: {} } | ||
] | ||
} | ||
}; | ||
``` | ||
|
||
描述这个dom: | ||
|
||
``` html | ||
<div id="container"> | ||
<input value="foo" type="text"> | ||
<a href="/bar"></a> | ||
<span></span> | ||
</div> | ||
``` | ||
|
||
--- | ||
|
||
`Didact-元素`与`React-元素`非常相似。 | ||
|
||
但是通常你在使用`React`时不会创建`React-元素`作为JS对象, | ||
|
||
你可能使用`JSX`或者甚至是`createElement`。 | ||
|
||
我们将在`Didact`中做同样的事情,但我们将会在系列下一篇文章中描述-`createElement`-的代码。 | ||
|
||
--- | ||
|
||
### 1.3 渲染-DOM-元素 | ||
|
||
下一步是将元素及其子元素呈现给dom。 | ||
|
||
我们将使用一个`render`函数(相当于`ReactDOM.render`)接收一个元素和一个`dom容器`。 | ||
|
||
该函数应该创建由`element`定义的`dom子树`并将其附加到`容器`中: | ||
|
||
``` js | ||
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`函数`迭代`属性名称并相应地-设置-它们: | ||
|
||
``` js | ||
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); | ||
} | ||
``` | ||
|
||
### 1.4 渲染DOM文本节点 | ||
|
||
`render`函数不支持的一件事是`文本节点`。首先,我们需要定义文本元素的外观。例如,`<span>Foo</span>`在`React`中描述的元素如下所示: | ||
|
||
``` js | ||
const reactElement = { | ||
type: "span", | ||
props: { | ||
children: ["Foo"] // 是孩子, 但也只是一个字符串 | ||
} | ||
}; | ||
``` | ||
|
||
请注意,`children`,只是一个字符串 ,而不是另一个元素对象。 | ||
|
||
这违背了我们如何定义`Didact元素`:`children`应该是元素的数组和所有元素应该有`type`和`props`。 | ||
|
||
如果我们遵循这些规则,我们将来会少一些`if`判断。 | ||
|
||
因此,`Didact Text Elements`将`type==“TEXT ELEMENT”`相等,实际文本将位于`nodeValue`属性中。 | ||
|
||
像这个: | ||
|
||
``` js | ||
const textElement = { | ||
type: "span", | ||
props: { | ||
children: [ | ||
{ | ||
type: "TEXT ELEMENT", // 1 | ||
props: { nodeValue: "Foo" } // 2 | ||
} | ||
] | ||
} | ||
}; | ||
``` | ||
|
||
现在我们已经规范了文本元素的数据结构,我们需要可以呈现它, 以便与其他元素一样,而区别也就是{`type: "TEXT ELEMENT"`}。 | ||
|
||
我们应该使用`createTextNode`,而不是使用`createElement`。 | ||
|
||
就是这样,`nodeValue`将会像其他属性一样设置。 | ||
|
||
``` js | ||
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); | ||
} | ||
``` | ||
|
||
### 1.5 概要 | ||
|
||
我们创建了一个`render函数`,允许我们将`一个元素{element}及其子元素{children}`呈现给-DOM「`parentDom.appendChild(dom);`」。 | ||
|
||
接下来我们需要的是`createElement`的简单方法。 | ||
|
||
我们将在下一篇文章中做到这一点,在那里我们将让`JSX与Didact`一起工作。 | ||
|
||
如果您想尝试我们迄今为止编写的代码,请检查[codepen](https://codepen.io/pomber/pen/eWbwBq?editors=0010)。你也可以从[github回购中检查这个差异](https://github.com/hexacta/didact/commit/fc4d360d91a1e68f0442d39dbce5b9cca5a08f24)。 | ||
|
||
--- | ||
|
||
下一篇文章:[Didact: Element creation and JSX {en}](https://engineering.hexacta.com/didact-element-creation-and-jsx-d05171c55c56) |-|_|🌟|[Didact:元素创建和JSX {zh}](#2-%E5%85%83%E7%B4%A0%E5%88%9B%E5%BB%BA%E5%92%8Cjsx) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
|
||
## 2. 元素创建和JSX | ||
|
||
> 这个故事是我们一步一步构建自己版本的React的系列文章的一部分: | ||
### 2.1 JSX | ||
|
||
上次我们介绍了[Didact Elements](#1.2-didact元素),它是一种描述我们想要呈现给-DOM-的非常详细的方式`{数据结构}`。 | ||
|
||
在这篇文章中,我们将看到如何使用`JSX`来简化元素的创建。 | ||
|
||
`JSX`提供了一些语法糖来创建元素。以便代替: | ||
|
||
``` js | ||
const element = { | ||
type: "div", | ||
props: { | ||
id: "container", | ||
children: [ | ||
{ type: "input", props: { value: "foo", type: "text" } }, | ||
{ | ||
type: "a", | ||
props: { | ||
href: "/bar", | ||
children: [{ type: "TEXT ELEMENT", props: { nodeValue: "bar" } }] | ||
} | ||
}, | ||
{ | ||
type: "span", | ||
props: { | ||
onClick: e => alert("Hi"), | ||
children: [{ type: "TEXT ELEMENT", props: { nodeValue: "click me" } }] | ||
} | ||
} | ||
] | ||
} | ||
}; | ||
``` | ||
|
||
我们的代码可以是 | ||
|
||
``` js | ||
const element = ( | ||
<div id="container"> | ||
<input value="foo" type="text" /> | ||
<a href="/bar">bar</a> | ||
<span onClick={e => alert("Hi")}>click me</span> | ||
</div> | ||
); | ||
``` | ||
|
||
如果你对`JSX`不熟悉,你可能会想知道最后一个片段是否是有效的`javascript:`它不是。 | ||
|
||
为了使浏览器的理解,需要的代码由预处理转化为-有效的JS,像babel[了解更多关于JSX阅读这篇文章由贾森·米勒](https://jasonformat.com/wtf-is-jsx/)。 | ||
|
||
例如,`babel`从上面将JSX转换为: | ||
|
||
``` js | ||
const element = createElement( | ||
"div", | ||
{ id: "container" }, | ||
createElement("input", { value: "foo", type: "text" }), | ||
createElement( | ||
"a", | ||
{ href: "/bar" }, | ||
"bar" | ||
), | ||
createElement( | ||
"span", | ||
{ onClick: e => alert("Hi") }, | ||
"click me" | ||
) | ||
); | ||
``` | ||
|
||
> [>> babel repl ](https://babeljs.io/repl/#?babili=false&evaluate=true&lineWrap=false&presets=react&targets=&browsers=&builtIns=false&debug=false&code=%2F**%20%40jsx%20createElement%20*%2F%0A%0Aconst%20element%20%3D%20%28%0A%20%20%3Cdiv%20id%3D%22container%22%3E%0A%20%20%20%20%3Cinput%20value%3D%22foo%22%20type%3D%22text%22%20%2F%3E%0A%20%20%20%20%3Ca%20href%3D%22%2Fbar%22%3Ebar%3C%2Fa%3E%0A%20%20%20%20%3Cspan%20onClick%3D%7Be%20%3D%3E%20alert%28%22Hi%22%29%7D%3Eclick%20me%3C%2Fspan%3E%0A%20%20%3C%2Fdiv%3E%0A%29%3B) | ||
我们需要添加到`Didact`中来支持`JSX`是一个`createElement`功能,这就是其余部分工作由-预处理器-完成的。 | ||
|
||
函数的第一个参数是`type`元素的第一个参数,第二个参数是元素的对象`props`,以及所有下面的参数`children`。 | ||
|
||
`createElement`需要创建一个`props`对象,将其分配给第二个参数中的所有值,将该`children`属性设置为第二个参数后面的所有参数,然后返回一个对象`{}` - 带有 `{type, props }`。把它放到代码中更容易: | ||
|
||
``` js | ||
function createElement(type, config, ...args) { | ||
const props = Object.assign({}, config);// 合并 | ||
const hasChildren = args.length > 0; // 孩子? | ||
props.children = hasChildren ? [].concat(...args) : []; | ||
return { type, props }; // Didact元素的数据结构-类型{type}与属性{props} | ||
} | ||
``` | ||
|
||
除了一件事情之外,这个函数运行良好:`文本元素`。 | ||
|
||
文本-作为字符串-传递给`createElement`函数,`Didact`需要文本元素`type`以及`props`其余元素。 | ||
|
||
所以我们将`每个arg`转换为一个文本元素-一个规范的`Didact元素: | ||
|
||
``` js | ||
const TEXT_ELEMENT = "TEXT ELEMENT"; // 类型 | ||
|
||
function createElement(type, config, ...args) { | ||
const props = Object.assign({}, config); | ||
const hasChildren = args.length > 0; | ||
const rawChildren = hasChildren ? [].concat(...args) : []; | ||
props.children = rawChildren | ||
.filter(c => c != null && c !== false) | ||
.map(c => c instanceof Object ? c : createTextElement(c)); | ||
// 过滤-空-值, 剩下的-不属于-Object的值 -> createTextElement -> 变为 类型为TEXT_ELEMENT- Didact元素 | ||
return { type, props }; | ||
} | ||
|
||
function createTextElement(value) { | ||
// 规范数据 | ||
return createElement(TEXT_ELEMENT, { nodeValue: value }); | ||
} | ||
``` | ||
|
||
我还筛选了要排除的子项列表`null,undefined并指出false`. | ||
|
||
我们不会呈现这些子项,因此不需要`添加`它们`props.children`。 | ||
|
||
|
||
### 2.2 概要 | ||
|
||
在这篇文章中我们没有给`Didact`增加任何实际的权力,但是我们现在有了改进的开发者体验, | ||
|
||
因为我们可以使用`JSX`来定义元素。我已经[更新了上次的codepen](https://codepen.io/pomber/pen/xdmoWE?editors=0010)以包含来自这篇文章的代码。 | ||
|
||
请注意,`codepen`使用`babel来传输JSX`,开头的注释`/** @jsx createElement */`告诉`babel`使用函数。 | ||
|
||
您还可以检查[Github提交的更改。](https://github.com/hexacta/didact/commit/15010f8e7b8b54841d1e2dd9eacf7b3c06b1a24b) | ||
|
||
--- | ||
|
||
在下一篇文章中,[Didact: Instances, reconciliation and virtual DOM](https://engineering.hexacta.com/didact-instances-reconciliation-and-virtual-dom-9316d650f1d0) |-|_|🌟| [我们介绍了Didact的虚拟DOM和协调算法以支持DOM更新](#3-%E5%AE%9E%E4%BE%8B-%E5%AF%B9%E6%AF%94%E5%92%8C%E8%99%9A%E6%8B%9Fdom) |
Oops, something went wrong.