您现在的位置是:网站首页> 内容页

从 0 到 1 实现 React 系列 —— JSX 和 Virtual DOM

  • youfa8备用线路
  • 2019-03-07
  • 346人已阅读
简介作者的博客看源码一个痛处是会陷进理不顺主干的困局中,本系列文章在实现一个(x)react的同时理顺React框架的主干内容(JSX/虚拟DOM/...)环境准备项目打包工具选择

作者的博客

看源码一个痛处是会陷进理不顺主干的困局中,本系列文章在实现一个 (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("/onw+/")) { // 处理事件的属性: 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) // 组件的挂载

项目说明

该系列文章会尽可能的分析项目细节,具体的还是以项目实际代码为准。

项目地址

文章评论

Top