请注意,这是React16.8的源码解析,当然他完全可以作为你阅读源码的参考,他还没有落后。
Step1
开始之前,要先了解一个知识点⬇️
我们都知道,要在JSX中写React语法,那为什么不能在js文件中写呢?也可以,但是你要使用相关的Babel转一下react语法,转成JS认识的语法。
换句话说,必须得有Babel将JSX转成JS,你的代码才能正常运行。
Step2
看一下这段代码
const element = <h1 title="foo">Hello</h1>
JS肯定无法识别这句话,但是用到Step1中的Babel转一下,JS就能识别了,那么转成了什么样子呢?让我们打印一下element⬇️
没错,变成了一个对象,记住这个type和props
你可以把type理解成标签名,props理解成这个node的所有属性
要注意:这个对象并不是Babel生成的,是React.createElement的方法生成的。
Babel只是告诉node要把这个react语法通过React.createElement方法转成js能识别的对象(这是我猜的)
Step3
让我们来重写一下精简版的React的createElement方法
function createTextElement(text) {
return {
type: "TEXT_ELEMENT",
props: {
nodeValue: text,
children: []
}
};
}
function createElement(type, props, ...children) {
return {
type,
props: {
...props,
children: children.map((chil) =>
typeof chil === "object" ? chil : createTextElement(chil)
)
}
};
}
这里的文本(TEXT_ELEMENT)对象好像和现在的略有不同(我没有看到现在React的TEXT_ELEMENT),不过它也不影响源码阅读。
让我们看几个dom节点生成了怎样的对象吧⬇️
const element = <h1 title="foo">Hello</h1>
⬇️⬇️⬇️
const element = React.createElement(
"h1",
{ title: "foo" },
"Hello"
)
⬇️⬇️⬇️
const element = {
type: "h1",
props: {
title: "foo",
children: "Hello",
},
}
const element = (
<div id="foo">
<a>bar</a>
<b />
</div>
)
⬇️⬇️⬇️
const element = React.createElement(
"div",
{ id: "foo" },
React.createElement("a", null, "bar"),
React.createElement("b")
)
⬇️⬇️⬇️
const element = {
type: "div",
props: {
title: "id",
children: [
{
type:"a",
props:{
children:"bar"
}
},
{
type:"b",
props:{}
},
],
},
}
Step4
接下来就是render了,将渲染完的对象element,渲染成真正的dom节点⬇️。
让我们重写一下精简版的ReactDom的render的方法,在18的版本中移除了它,也可以继续参考。
function render(element, container) {
const { type, props } = element;
// 根据type创建节点
const dom =
type !== "TEXT_ELEMENT"
? document.createElement(type)
: document.createTextNode("");
// 将属性添加到节点
Object.keys(props)
.filter((key) => key !== "children")
.forEach((name) => (dom[name] = props[name]));
// 递归增加子节点
props.children.forEach((chil) => render(chil, dom));
// 添加节点
container.appendChild(dom);
}
Step5
接下来,你只需要像初始化react项目时,把你的App挂载到root上就可以了。
const element = (
<div style="background: salmon">
<h1>Hello World</h1>
<h2 style="text-align:right">from Didact</h2>
</div>
);
const container = document.getElementById("root");
Lee.render(element, container);
你可以看一下这个sandbox来验证你的学习成果。
这是我的完整练习代码⬇️
function createTextElement(text) {
return {
type: "TEXT_ELEMENT",
props: {
nodeValue: text,
children: []
}
};
}
function createElement(type, props, ...children) {
return {
type,
props: {
...props,
children: children.map((chil) =>
typeof chil === "object" ? chil : createTextElement(chil)
)
}
};
}
function render(element, container) {
const { type, props } = element;
const dom =
type !== "TEXT_ELEMENT"
? document.createElement(type)
: document.createTextNode("");
Object.keys(props)
.filter((key) => key !== "children")
.forEach((name) => (dom[name] = props[name]));
props.children.forEach((chil) => render(chil, dom));
container.appendChild(dom);
}
const Lee = {
render,
createElement
};
/** @jsx Lee.createElement */
const element = (
<div style="background: salmon">
<h1>Hello World</h1>
<h2 style="text-align:right">from Didact</h2>
</div>
);
const container = document.getElementById("root");
Lee.render(element, container);