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

环境准备

项目打包工具选择了 parcel,使用其可以快速地进入项目开发的状态。快速开始

此外需要安装以下 babel 插件:

  1. "babel-core": "^6.26.0",
  2. "babel-preset-env": "^1.6.1",
  3. "babel-plugin-transform-react-jsx": "^6.24.1"

同时 .babelrc 配置如下:

  1. {
  2. "presets": ["env"],
  3. "plugins": [
  4. // 插件如其名:转化 JSX 语法为定义的形式
  5. ["transform-react-jsx", {
  6. "pragma": "React.createElement"
  7. }]
  8. ]
  9. }

JSX 和 虚拟 DOM

  1. const element = (
  2. <div className="title">
  3. hello<span className="content">world!</span>
  4. </div>
  5. )

JSX 是一种语法糖,经过 babel 转换结果如下,可以发现实际上转化成 React.createElement() 的形式:

  1. var element = React.createElement(
  2. "div",
  3. { className: "title" },
  4. "hello",
  5. React.createElement(
  6. "span",
  7. { className: "content" },
  8. "world!"
  9. )
  10. );

打印 element, 结果如下:

  1. {
  2. attributes: {className: "title"}
  3. children: ["hello", t] // t 和外层对象相同
  4. key: undefined
  5. nodeName: "div"
  6. }

因此,我们得出结论:JSX 语法糖经过 Babel 编译后转换成一种对象,该对象即所谓的虚拟 DOM,使用虚拟 DOM 能让页面进行更为高效的渲染。

我们按照这种思路进行函数的构造:

  1. const React = {
  2. createElement
  3. }
  4. function createElement(tag, attr, ...child) {
  5. return {
  6. attributes: attr,
  7. children: child,
  8. key: undefined,
  9. nodeName: tag,
  10. }
  11. }
  12. // 测试
  13. const element = (
  14. <div className="title">
  15. hello<span className="content">world!</span>
  16. </div>
  17. )
  18. console.log(element) // 打印结果符合预期
  19. // {
  20. // attributes: {className: "title"}
  21. // children: ["hello", t] // t 和外层对象相同
  22. // key: undefined
  23. // nodeName: "div"
  24. // }

虚拟 DOM 转化为真实 DOM

上个小节介绍了 JSX 转化为虚拟 DOM 的过程,这个小节接着来实现将虚拟 DOM 转化为真实 DOM (页面上渲染的是真实 DOM)。

我们知道在 React 中,将虚拟 DOM 转化为真实 DOM 是使用 ReactDOM.render 实现的,使用如下:

  1. ReactDOM.render(
  2. element, // 上文的 element,即虚拟 dom
  3. document.getElementById('root')
  4. )

接着来实现 ReactDOM.render 的逻辑:

  1. const ReactDOM = {
  2. render
  3. }
  4. /**
  5. * 将虚拟 DOM 转化为真实 DOM
  6. * @param {*} vdom 虚拟 DOM
  7. * @param {*} container 需要插入的位置
  8. */
  9. function render(vdom, container) {
  10. if (typeof(vdom) === 'string') {
  11. container.innerText = vdom
  12. return
  13. }
  14. const dom = document.createElement(vdom.nodeName)
  15. for (let attr in vdom.attributes) {
  16. setAttribute(dom, attr, vdom.attributes[attr])
  17. }
  18. vdom.children.forEach(vdomChild => render(vdomChild, dom))
  19. container.appendChild(dom)
  20. }
  21. /**
  22. * 给节点设置属性
  23. * @param {*} dom 操作元素
  24. * @param {*} attr 操作元素属性
  25. * @param {*} value 操作元素值
  26. */
  27. function setAttribute(dom, attr, value) {
  28. if (attr === 'className') {
  29. attr = 'class'
  30. }
  31. if (attr.match('/on\w+/')) { // 处理事件的属性:
  32. const eventName = attr.toLowerCase().splice(1)
  33. dom.addEventListener(eventName, value)
  34. } else if (attr === 'style') { // 处理样式的属性:
  35. let styleStr = ''
  36. let standardCss
  37. for (let klass in value) {
  38. standardCss = humpToStandard(klass) // 处理驼峰样式为标准样式
  39. styleStr += `${standardCss}: ${value[klass]};`
  40. }
  41. dom.setAttribute(attr, styleStr)
  42. } else { // 其它属性
  43. dom.setAttribute(attr, value)
  44. }
  45. }

至此,我们成功将虚拟 DOM 复原为真实 DOM,展示如下:

另外配合热更新,在热更新的时候清空之前的 dom 元素,改动如下:

  1. const ReactDOM = {
  2. render(vdom, container) {
  3. container.innerHTML = null
  4. render(vdom, container)
  5. }
  6. }

总结

JSX 经过 babel 编译为 React.createElement() 的形式,其返回结果就是 Virtual DOM,最后通过 ReactDOM.render() 将 Virtual DOM 转化为真实的 DOM 展现在界面上。流程图如下:

思考题

如下是一个 react/preact 的常用组件的写法,那么为什么要 import 一个 React 或者 h 呢?

  1. import React, { Component } from 'react' // react
  2. // import { h, Component } from 'preact' // preact
  3. class A extends Component {
  4. render() {
  5. return <div>I'm componentA</div>
  6. }
  7. }
  8. 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. Supervisor 为服务创建守护进程

    今天需要再服务上部署一个.net 方面的项目:当时开启服务的命令只能在前台执行:使用nohub CMD &等放在后台开启服务都会宕机:所以搜寻了Supervisor 这个解决办法,为服务创建守 ...

  2. Java提高班(二)深入理解线程池ThreadPool

    本文你将获得以下信息: 线程池源码解读 线程池执行流程分析 带返回值的线程池实现 延迟线程池实现 为了方便读者理解,本文会由浅入深,先从线程池的使用开始再延伸到源码解读和源码分析等高级内容,读者可根据 ...

  3. SpringCloud系列——Ribbon 负载均衡

    前言 Ribbon是一个客户端负载均衡器,它提供了对HTTP和TCP客户端的行为的大量控制.我们在上篇(猛戳:SpringCloud系列——Feign 服务调用)已经实现了多个服务之间的Feign调用 ...

  4. 基于C#程序设计语言的三种组合算法

    目录 基于C#程序设计语言的三种组合算法 1. 总体思路 1.1 前言 1.2 算法思路 1.3 算法需要注意的点 2. 三种组合算法 2.1 普通组合算法 2.2 与自身进行组合的组合算法 2.3 ...

  5. C# 如何创建Excel多级分组

    在Excel中如果能够将具有多级明细的数据进行分组显示,可以清晰地展示数据表格的整体结构,使整个文档具有一定层次感.根据需要设置显示或者隐藏分类数据下的详细信息,在便于数据查看.管理的同时也使文档更具 ...

  6. 接口测试之深入理解HTTPS

    前言 随着网络安全问题越来越被重视,HTTPS协议的使用已经逐渐主流化.目前的主流站点均已使用了HTTPS协议:比如:百度.淘宝.京东等一二线主站都已经迁移到HTTPS服务之上.而作为测试人员来讲,也 ...

  7. c++中求数组长度

    #include <iostream> using namespace std; template <class T> int getArrSize(T& arr){ ...

  8. SD详解-销售过程

    http://www.sapzh.com/html/Financial/SD/3113.html 7  销售过程 7.1销售执行 目的 作用与目的描述           步骤 操作过程/步骤描述 备 ...

  9. 诗人般的机器学习,ML工作原理大揭秘

    诗人般的机器学习,ML工作原理大揭秘 https://mp.weixin.qq.com/s/7N96aPAM_M6t0rV0yMLKbg 选自arXiv 作者:Cassie Kozyrkov 机器之心 ...

  10. IBM Watson启示录:AI不应该仅仅是炫技

    IBM Watson启示录:AI不应该仅仅是炫技 https://mp.weixin.qq.com/s/oNp8QS7vQupbi8fr5RyLxA                         导 ...