const element = (<div className="title">hello<span className="content">world!</span></div>)
JSX 是一种语法糖, 经过 babel 转换结果如下, 可以发现实际上转化成 React.createElement()
的形式:
扩展: babel 执行机制
var element = React.createElement("div",{ className: "title" },"hello",React.createElement("span",{ className: "content" },"world!"));
打印 element, 结果如下:
{attributes: {className: "title"}children: ["hello", t] // t 和外层对象相同key: undefinednodeName: "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"// }
上个小节介绍了 JSX 转化为虚拟 DOM 的过程, 这个小节接着来实现将虚拟 DOM 转化为真实 DOM (页面上渲染的是真实 DOM)。
我们知道在 React 中, 将虚拟 DOM 转化为真实 DOM 是使用 ReactDOM.render
实现的, 使用如下:
import ReactDOM from 'react-dom'ReactDOM.render(element, // 上文的 element, 即虚拟 domdocument.getElementById('root'))
接着来实现 ReactDOM.render
的逻辑:
const ReactDOM = {render}/*** 将虚拟 DOM 转化为真实 DOM* @param {*} vdom 虚拟 DOM* @param {*} container 需要插入的位置*/function render(vdom, container) {if (_.isString(vdom) || _.isNumber(vdom)) {container.innerText = container.innerText + vdom // fix <div>I'm {this.props.name}</div>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().substr(2)dom.addEventListener(eventName, value)} else if (attr === 'style') { // 处理样式的属性:let styleStr = ''let standardCssfor (let klass in value) {standardCss = humpToStandard(klass) // 处理驼峰样式为标准样式value[klass] = _.isNumber(+value[klass]) ? value[klass] + 'px' : value[klass] // style={{ className: '20' || '20px' }}>styleStr += `${standardCss}: ${value[klass]};`}dom.setAttribute(attr, styleStr)} else { // 其它属性dom.setAttribute(attr, value)}}
至此, 我们成功将虚拟 DOM 复原为真实 DOM, 展示如下:
另外配合热更新, 在热更新的时候清空之前的 dom 元素, 改动如下:
const ReactDOM = {render(vdom, container) {container.innerHTML = nullrender(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) // 组件的挂载