看源码一个痛处是会陷进理不顺主干的困局中,本系列文章在实现一个 (x)react 的同时理顺 React 框架的主干内容(JSX/虚拟DOM/组件/生命周期/diff算法/setState/ref/...)

环境准备

项目打包工具选择了 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' // preact class A extends Component {
render() {
return <div>I'm componentA</div>
}
} render(<A />, document.body) // 组件的挂载

项目说明

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

项目地址

从 0 到 1 实现 React 系列 —— 1.JSX 和 Virtual DOM的更多相关文章

  1. 从 0 到 1 实现 React 系列 —— 5.PureComponent 实现 && HOC 探幽

    本系列文章在实现一个 cpreact 的同时帮助大家理顺 React 框架的核心内容(JSX/虚拟DOM/组件/生命周期/diff算法/setState/PureComponent/HOC/...) ...

  2. 从 0 到 1 实现 React 系列 —— 4.setState优化和ref的实现

    看源码一个痛处是会陷进理不顺主干的困局中,本系列文章在实现一个 (x)react 的同时理顺 React 框架的主干内容(JSX/虚拟DOM/组件/生命周期/diff算法/setState/ref/. ...

  3. 从 0 到 1 实现 React 系列 —— 3.生命周期和 diff 算法

    看源码一个痛处是会陷进理不顺主干的困局中,本系列文章在实现一个 (x)react 的同时理顺 React 框架的主干内容(JSX/虚拟DOM/组件/生命周期/diff算法/setState/ref/. ...

  4. 从 0 到 1 实现 React 系列 —— 2.组件和 state|props

    看源码一个痛处是会陷进理不顺主干的困局中,本系列文章在实现一个 (x)react 的同时理顺 React 框架的主干内容(JSX/虚拟DOM/组件/生命周期/diff算法/setState/ref/. ...

  5. 学习React系列(六)——更新dom细节于原理

    React更新dom的依据: 1.不同类型的elements会产生不同的树 2.通过render方法中包含key属性的子元素,开发者可以示意哪些子元素可能是稳定的. 更新过程: 一.根元素类型不同:旧 ...

  6. React系列,jsx

    <script type="text/babel"> var name = "kimoo"; var fn = ()=> "kimo ...

  7. 前端笔记之React(四)生命周期&Virtual DOM和Diff算法&日历组件开发

    一.React生命周期 一个组件从出生到消亡,在各个阶段React提供给我们调用的接口,就是生命周期. 生命周期这个东西,必须有项目,才知道他们干嘛的. 1.1 Mouting阶段[装载过程] 这个阶 ...

  8. 从0到1用react+antd+redux搭建一个开箱即用的企业级管理后台系列(基础篇)

    背景 ​ 最近因为要做一个新的管理后台项目,新公司大部分是用vue写的,技术栈这块也是想切到react上面来,所以,这次从0到1重新搭建一个react项目架子,需要考虑的东西的很多,包括目录结构.代码 ...

  9. React 系列教程 1:实现 Animate.css 官网效果

    前言 这是 React 系列教程的第一篇,我们将用 React 实现 Animate.css 官网的效果.对于 Animate.css 官网效果是一个非常简单的例子,原代码使用 jQuery 编写,就 ...

随机推荐

  1. Spring Boot 系列总目录

    一.Spring Boot 系列诞生原因 上学那会主要学的是 Java 和 .Net 两种语言,当时对于语言分类这事儿没什么概念,恰好在2009年毕业那会阴差阳错的先找到了 .Net 的工作,此后就开 ...

  2. java中变量的存储位置

    1.寄存器:最快的存储区, 由编译器根据需求进行分配,我们在程序中无法控制. 2. 栈:存放基本类型的变量数据和对象的引用,但对象本身不存放在栈中,而是存放在堆(new 出来的对象)或者常量池中(字符 ...

  3. win2008R2环境配置

    1)下载系统  [https://msdn.itellyou.cn/]  选择带sp1的,这个系统是08R2中较为完善的一种 2)安装之后首先不要激活和安装.net framework   先安装ii ...

  4. C# 处理Excel公式(一)——创建、读取Excel公式

    对于数据量较大的表格,需要计算一些特殊数值时,我们通过运用公式能有效提高我们数据处理的速度和效率,对于后期数据的增删改查等的批量操作也很方便.此外,对于某些数值的信息来源,我们也可以通过读取数据中包含 ...

  5. CSS3 calc实现滚动条出现页面不跳动

    什么是calc()? calc()从字面我们可以把他理解为一个函数function.其实calc是英文单词calculate(计算)的缩写,是css3的一个新增的功能,用来指定元素的长度.比如说,你可 ...

  6. i的二次幂求和

    \(i^2\)求和 老祖宗告诉我们\(\sum_{i=1}^n i^2 = \frac{n(n+1)(2n+1)}{6}\) 但是这玩意儿是怎么出来的呢?感觉网上用立方差证明的思路太low了,今天偶然 ...

  7. 【Webpack 杂谈】帮助文档翻译:Webpack的模块

    页面出自Webpack官方文档(撰写时,是v4.1.1) 其实Webpack本身有中文文档,不知道是谁去撰写的,但是自己翻译一遍感觉更好理解. https://webpack.js.org/conce ...

  8. WPF:实现自定义标记扩展

    标记扩展使用{标记扩展类 参数}语法,如: <TextBlock Text={x:Null}/> 为什么x:Null就可以返回一个null值呢? 其实在System.Windows.Mar ...

  9. Android Studio遇到Failed to sync Gradle project错误时的解决办法

    一   报错显示 Gradle sync failed: Unknown host 'd29vzk4ow07wi7.cloudfront.net'. You may need to adjust th ...

  10. TCP/IP、UDP、HTTP、SOCKET详解

    文章大纲 网络OSI七层及各层作用 TCP与UDP基本介绍 TCP连接过程详解 SOCKET原理与连接详解     一.网络OSI七层及各层作用   应用层:文件传输,电子邮件,文件服务,虚拟终端 T ...