本篇文章参考了Build your own React
首先,我们需要先create-react-app创建项目,并修改下入口文件index.js
import React from "react"
import ReactDOM from "react-dom"
const element = <h1 title="foo">Hello</h1>
const container = document.getElementById("root")
ReactDOM.render(element, container)
可以来分成三步来解析下代码(暂且忽略import代码)
第一步
const element = <h1 title="foo">Hello</h1>
这是一个jsx语法,它是通过babel-plugin-transform-react-jsx来进行转换的,可以看下转换成js的形式
const element = React.createElement(
"h1",
{ title: "foo" },
"Hello"
)
我们可以将它抽象的转化成数据结构的形式
const element = {
type: "h1",
props: {
title: "foo",
children: "Hello",
},
}
然后接下来我们可以通过不使用babel转译的方法来实现原本的逻辑
/* 解析代码 */
// const element = <h1 title="foo">Hello</h1>
//抽象转化为数据结构形式
const element = {
type: "h1",
props: {
title: "foo",
children: "Hello",
},
}
//根据结构中的内容创建h1
const node = document.createElement(element.type)
node["title"] = element.props.title
//根据子元素创建文本节点并插入数据
const text = document.createTextNode("")
text["nodeValue"] = element.props.children
第二步(可以不用管)
const container = document.getElementById("root")
第三步
将创建好的元素插入到root里面
/* 解析代码 */
// ReactDOM.render(element, container)
//将创建好的元素插入到root里面
node.appendChild(text)
container.appendChild(node)
可以看到结果,其中我们并没有使用React,但是我们也实现了同样的功能
接下来,我们可以尝试对更复杂的逻辑进行一个封装
例如像下面这种嵌套类型的
const element = (
<div id="foo">
<a>bar</a>
<b />
</div>
)
const container = document.getElementById("root")
ReactDOM.render(element, container)
根据
const node = document.createElement(element.type)
node["title"] = element.props.title
const text = document.createTextNode("")
text["nodeValue"] = element.props.children
node.appendChild(text)
container.appendChild(node)
我们可以知道createELement接收要创建节点的type
,节点的属性props
以及可以包含的children
元素(children
这里我们需要注意,当子元素并不是一个标签时,我们并没有办法拿到他的标签type,但我们又希望children
内部包含的都是标签,所以我们需要进行一步转换)
//尝试封装一下createElement函数
//使用拓展运算符获取后续所有的children
function createElement(type, props, ...children) {
//元素是带有type和 的对象props。此函数唯一需要做的就是创建那个对象
return {
type,
props: {
...props,
// 如果不是对象,则先默认他为一个需要创建标签的字符串元素
children: children.map((child) => (typeof child === "object" ? child : createTextElement(child))),
},
}
}
function createTextElement(text) {
return {
type: "TEXT_ELEMENT",
props: {
nodeValue: "text",
children: [],
},
}
}
为了替换React的createElement,给我们的react起个名字: LanReact
const LanReact = {
createElement,
}
const element = LanReact.createElement("div", { id: "foo" }, LanReact.createElement("a", null, "bar"), LanReact.createElement("b"))
如果通过babel转译成jsx的话,他是这样的
const element = (
<div id="foo">
<a>bar</a>
<b />
</div>
)
上面的部分完成了React中的createElement的编写,但并没有渲染出来
同样需要将render加入到我们自己的react中
function render(element, container) {
//创建dom节点
}
const LanReact = {
createElement,
render
}
接下来就需要完善render中的逻辑了
function render(element, container) {
//创建dom节点
//需要处理文本元素,如果元素类型是TEXT_ELEMENT我们创建文本节点而不是常规节点
const dom = element.type == "TEXT_ELEMENT" ? document.createTextNode("") : document.createElement(element.type)
// 到这里我们只使用了数据结构(type/props/children)中的type,我们还需要将props挂载到创建的dom上。
//因为children同样存在于props中,我们要将其过滤
const isProperty = (key) => key !== "children"
Object.keys(element.props)
.filter(isProperty)
.forEach((name) => {
// 将过滤后的props对dom进行设置
dom[name] = element.props[name]
})
// 为每个子元素执行同样的render操作,渲染他们每一个,同样的,第一个参数为元素数据,第二个参数为要挂载的目标节点
element.props.children.forEach((child) => render(child, dom))
container.appendChild(dom)
}
现在我们就可以使用自己的React来仿照jsx的语法了
完整代码
import React from "react"
import ReactDOM from "react-dom"
// const element = (
// <div id="foo">
// <a>bar</a>
// <b />
// </div>
// )
// const container = document.getElementById("root")
// ReactDOM.render(element, container)
/* 解析代码 */
// const element = (
// <div id="foo">
// <a>bar</a>
// <b />
// </div>
// )
// 使用 React 的createElement.
// const element = React.createElement("div", { id: "foo" }, React.createElement("a", null, "bar"), React.createElement("b"))
// 为了替换React的createElement,给我们的react起个名字: LanReact
const LanReact = {
createElement,
render,
}
const element = LanReact.createElement("div", { id: "foo" }, LanReact.createElement("a", null, "bar"), LanReact.createElement("b"))
//如果通过babel转译成jsx的话,他是这样的
/* const element = (
<div id="foo">
<a>bar</a>
<b />
</div>
) */
//尝试封装一下createElement函数
//使用拓展运算符获取后续所有的children
function createElement(type, props, ...children) {
//元素是带有type和 的对象props。此函数唯一需要做的就是创建那个对象
return {
type,
props: {
...props,
// 如果不是对象,则先默认他为一个需要创建标签的字符串元素
children: children.map((child) => (typeof child === "object" ? child : createTextElement(child))),
},
}
}
function createTextElement(text) {
return {
type: "TEXT_ELEMENT",
props: {
nodeValue: "text",
children: [],
},
}
}
const container = document.getElementById("root")
//上面的部分完成了React中的createElement的编写,但并没有渲染出来
function render(element, container) {
//创建dom节点
//需要处理文本元素,如果元素类型是TEXT_ELEMENT我们创建文本节点而不是常规节点
const dom = element.type == "TEXT_ELEMENT" ? document.createTextNode("") : document.createElement(element.type)
// 到这里我们只使用了数据结构(type/props/children)中的type,我们还需要将props挂载到创建的dom上。
//因为children同样存在于props中,我们要将其过滤
const isProperty = (key) => key !== "children"
Object.keys(element.props)
.filter(isProperty)
.forEach((name) => {
// 将过滤后的props对dom进行设置
dom[name] = element.props[name]
})
// 为每个子元素执行同样的render操作,渲染他们每一个,同样的,第一个参数为元素数据,第二个参数为要挂载的目标节点
element.props.children.forEach((child) => render(child, dom))
container.appendChild(dom)
}
//同样需要将render加入到我们自己的react中
//调用
LanReact.render(element, container)
//OK,接下来就可以直接使用自己的react了 ---> Demo2
此处仿照Build your own React的例子
function createElement(type, props, ...children) {
return {
type,
props: {
...props,
children: children.map((child) => (typeof child === "object" ? child : createTextElement(child))),
},
}
}
function createTextElement(text) {
return {
type: "TEXT_ELEMENT",
props: {
nodeValue: text,
children: [],
},
}
}
function render(element, container) {
const dom = element.type == "TEXT_ELEMENT" ? document.createTextNode("") : document.createElement(element.type)
const isProperty = (key) => key !== "children"
Object.keys(element.props)
.filter(isProperty)
.forEach((name) => {
dom[name] = element.props[name]
})
element.props.children.forEach((child) => render(child, dom))
container.appendChild(dom)
}
const LanReact = {
createElement,
render,
}
/** @jsx LanReact.createElement */
const element = (
<div style="background: salmon">
<h1>Hello World</h1>
<h2 style="text-align:right">from LanReact</h2>
</div>
)
const container = document.getElementById("root")
LanReact.render(element, container)
注意,可能会有很多人在此处运行时会出现报错
解决办法就是添加一下jsx的转换模式
/** @jsxRuntime classic */
/** @jsx LanReact.createElement */
const element = (
<div style="background: salmon">
<h1>Hello World</h1>
<h2 style="text-align:right">from LanReact</h2>
</div>
)
const container = document.getElementById("root")
LanReact.render(element, container)
顺便提一下我在create-react-app时遇到并解决的一个疑问吧,为什么入口文件必须是index.js
这个问题可以直接看下package.json里的启动文件
顺着node_modules找到config/path.js就可以了
此处配置的
如果想修改入口文件的话,直接修改并重新启动项目就可以了,注意是有三处需要修改的