写一个自己的React

本篇文章参考了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就可以了

此处配置的

 如果想修改入口文件的话,直接修改并重新启动项目就可以了,注意是有三处需要修改的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值