看源码一个痛处是会陷进理不顺主干的困局中,本系列文章在实现一个 (x)react 的同时理顺 React 框架的主干内容(JSX/虚拟DOM/...)
环境准备
项目打包工具选择了 parcel,使用其可以快速地进入项目开发的状态。快速开始
此外需要安装以下 babel 插件:
"babel-core": "^6.26.0","babel-preset-env": "^1.6.1","babel-plugin-transform-react-jsx": "^6.24.1"
同时 .babelrc 配置如下:
{ "presets": ["env"], "plugins": [ // 插件如其名:转化 JSX 语法为定义的形式
["transform-react-jsx", { "pragma": "React.createElement"
}]
]
}JSX 和 虚拟 DOM
const element = ( <div className="title"> hello<span className="content">world!</span> </div>)
JSX 是一种语法糖,经过 babel 转换结果如下,可以发现实际上转化成 React.createElement() 的形式:
var element = React.createElement( "div",
{ className: "title" }, "hello",
React.createElement( "span",
{ className: "content" }, "world!"
)
);打印 element, 结果如下:
{
attributes: {className: "title"}
children: ["hello", t] // t 和外层对象相同
key: undefined
nodeName: "div"}因此,我们得出结论:JSX 语法糖经过 Babel 编译后转换成一种对象,该对象即所谓的虚拟 DOM,使用虚拟 DOM 能让页面进行更为高效的渲染。
我们按照这种思路进行函数的构造:
const React = {
createElement
}function createElement(tag, attr, ...child) { return {
attributes: attr,
children: child,
key: undefined,
nodeName: tag,
}
}// 测试const element = ( <div className="title">
hello<span className="content">world!</span>
</div>)console.log(element) // 打印结果符合预期// {// attributes: {className: "title"}// children: ["hello", t] // t 和外层对象相同// key: undefined// nodeName: "div"// }虚拟 DOM 转化为真实 DOM
上个小节介绍了 JSX 转化为虚拟 DOM 的过程,这个小节接着来实现将虚拟 DOM 转化为真实 DOM (页面上渲染的是真实 DOM)。
我们知道在 React 中,将虚拟 DOM 转化为真实 DOM 是使用 ReactDOM.render 实现的,使用如下:
ReactDOM.render(
element, // 上文的 element,即虚拟 dom
document.getElementById('root')
)接着来实现 ReactDOM.render 的逻辑:
const ReactDOM = {
render
}/**
* 将虚拟 DOM 转化为真实 DOM
* @param {*} vdom 虚拟 DOM
* @param {*} container 需要插入的位置
*/function render(vdom, container) { if (typeof(vdom) === 'string') {
container.innerText = vdom return
} const dom = document.createElement(vdom.nodeName) for (let attr in vdom.attributes) {
setAttribute(dom, attr, vdom.attributes[attr])
}
vdom.children.forEach(vdomChild => render(vdomChild, dom))
container.appendChild(dom)
}/**
* 给节点设置属性
* @param {*} dom 操作元素
* @param {*} attr 操作元素属性
* @param {*} value 操作元素值
*/function setAttribute(dom, attr, value) { if (attr === 'className') {
attr = 'class'
} if (attr.match('/on\w+/')) { // 处理事件的属性:
const eventName = attr.toLowerCase().splice(1)
dom.addEventListener(eventName, value)
} else if (attr === 'style') { // 处理样式的属性:
let styleStr = ''
let standardCss for (let klass in value) {
standardCss = humpToStandard(klass) // 处理驼峰样式为标准样式
styleStr += `${standardCss}: ${value[klass]};`
}
dom.setAttribute(attr, styleStr)
} else { // 其它属性
dom.setAttribute(attr, value)
}
}至此,我们成功将虚拟 DOM 复原为真实 DOM,展示如下:

另外配合热更新,在热更新的时候清空之前的 dom 元素,改动如下:
const ReactDOM = {
render(vdom, container) {
container.innerHTML = null
render(vdom, container)
}
}总结
JSX 经过 babel 编译为 React.createElement() 的形式,其返回结果就是 Virtual DOM,最后通过 ReactDOM.render() 将 Virtual DOM 转化为真实的 DOM 展现在界面上。流程图如下:

思考题
如下是一个 react/preact 的常用组件的写法,那么为什么要 import 一个 React 或者 h 呢?
import React, { Component } from 'react' // react// import { h, Component } from 'preact' // preactclass A extends Component {
render() { return <div>I'm componentA</div>
}
}
render(<A />, document.body) // 组件的挂载项目说明
该系列文章会尽可能的分析项目细节,具体的还是以项目实际代码为准。
作者:牧云云
出处:http://www.cnblogs.com/MuYunyun/"
本文版权归作者和博客园所有,欢迎转载,转载请标明出处。
随时随地看视频