前言
工作中经常使用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
。
附上本文代码地址