实现一个react系列一:JSX和虚拟DOM
前言
本文主要参考了从零开始实现一个React和从 0 到 1 实现React
工作中经常使用react
,对于react
中的一些虚拟DOM
、生命周期、组件等概念知其然,不知其所以然。虽然知道这些怎么用的就足够应付大部分的工作,但是作为一个开发者,还是要有追求的。所以有了这个系列,一步一步实现一个简单的react
出来。
语法糖JSX
我们平时在react
中写的JSX
,其实是一种语法糖,会被babel
转换成React.createElement()
。我们可以在babel官网上做个实验,看下JSX
会被babel
转换成什么。下面是个简单的例子。
我们可以看到,JSX
会被转成React.createElement(tag, attrs, child1, child2, ...)
这种形式。那我们只要装个babel
插件,然后写个createElement
方法就可以处理JSX
代码了。
createElement方法和虚拟DOM
-
准备
首先安装babel
模块,babel
的.babelrc
配置如下:
{
"presets": ["env"],
"plugins": [
[
"transform-react-jsx",
{
"pragma": "React.createElement"
}
]
]
}
使用打包工具parcel,webpack
也可以。比较懒,使用了parcel来打包代码。
-
实现
上面我们已经提到了JSX
会被babel
转换为React.createElement(tag, attrs, child1, child2, ...)
。
第一个参数:是元素的标签名,可以是div、span
等。
第二个参数:是元素的属性名,可以是className
、onClick
等。
之后的参数,是元素的子节点。
所以我们实现一个函数createElement
,接受上面的参数,然后将这些参数返回就可以了。
const createElement = (tag, attrs, ...childs) => {
return { tag, attrs, childs }
}
看下效果如何。
const React = {
createElement
}
const title = (
<div className="title">
<p>Hello, world!</p>
</div>
)
console.log(title)
打开Chrome的控制台,我们可以看到差不多是我们想要的。
createElement
方法返回的对象就是虚拟DOM
,这个对象中记录了该DOM
节点的所有信息,根据这些信息我们可以将虚拟DOM
转化为真实的DOM
。
将虚拟DOM渲染成真实DOM
在react中,将vdom渲染成真实的DOM,我们使用的是ReactDOM.render,像这样。
ReactDOM.render(
<div>Hello, world!</div>, // 这个会被转化为vdom
document.getElementById('root') // 获取根节点
)
我们可以看出,render
实现的功能是将vdom
转化为真实的dom
,挂载到根节点上。明白这一点,代码就很好写了。
const ReactDom = {
render
}
// 将 vdom 转换为真实 dom
const render = (vdom, root) => {
if (typeof vdom === "string") {
// 子元素如果是字符串,直接拼接字符串
root.innerText += vdom
return
}
const dom = document.createElement(vdom.tag)
if (vdom.attrs) {
for (let attr in vdom.attrs) {
const value = vdom.attrs[attr]
setAttribute(dom, attr, value)
}
}
// 遍历子节点
vdom.childs.forEach(child => render(child, dom))
// 将子元素挂载到其真实 DOM 的父元素上
root.appendChild(dom)
}
// 设置 dom 节点属性
const setAttribute = (dom, attr, value) => {
if (attr === "className") {
attr = "class"
}
// 处理事件
if (/on\w+/.test(attr)) {
attr = attr.toLowerCase()
dom[attr] = value || ""
} else if (attr === "style" && value) {
// 处理 style 样式,可以是个字符串或者对象
if (typeof value === "string") {
dom.style.cssText = value
} else if (typeof value === "object") {
for (let styleName in value) {
dom.style[styleName] =
typeof value[styleName] === "number"
? value[styleName] + "px"
: value[styleName]
}
}
} else {
// 其他属性
dom.setAttribute(attr, value)
}
}
这样我们就将虚拟dom渲染成真实的dom,考虑到热更新,我们需要在render之前先清除下root节点下的内容。
const ReactDOM = {
render: (vdom, root) => {
root.innerText = ""
render(vdom, root)
}
}
总结
在react
中,jsx
会被babel
转化为React.createElement(标签、属性、子元素1、子元素2、...)
形式,该函数返回一个对象,即虚拟dom
。然后ReactDOM.render()
,会将虚拟dom
转化为真实的dom
。
附上本文代码地址