在上一篇文章中,我们介绍了Babel是如何将JSX代码编译成可执行代码的,随后也实现了一个自己的解析器,模拟了Babel编译的过程。

现在我们再来回顾一下,假定有如下业务代码:

const style = {
  color: 'red',
  fontSize: '20px',
};

const greet = function (name) {
  return `hello ${name}`;
};

const App = (
  <div className="container">
    <p style={style}>saying {greet('scott')} hah</p>
    <div>
      <p>this is jsx-like code</p>
      <i className="icon"/>
      <p>parsing it now</p>
      <img className="icon"/>
    </div>
    <input type="button" value="i am a button"/>
    <em/>
  </div>
);

console.log(App);

ReactDOM.render(App, document.getElementById('root'));

经过编译之后,会生成下面的可执行代码:

var style = {
  color: 'red',
  fontSize: '20px'
};

var greet = function greet(name) {
  return 'hello ' + name;
};

var App = React.createElement(
  'div',
  { className: 'container' },
  React.createElement(
    'p',
    { style: style },
    'saying ',
    greet('scott'),
    ' hah'
  ),
  React.createElement(
    'div',
    null,
    React.createElement(
      'p',
      null,
      'this is jsx-like code'
    ),
    React.createElement('i', { className: 'icon' }),
    React.createElement(
      'p',
      null,
      'parsing it now'
    ),
    React.createElement('img', { className: 'icon' })
  ),
  React.createElement('input', { type: 'button', value: 'i am a button' }),
  React.createElement('em', null)
);

console.log(App);

ReactDOM.render(App, document.getElementById('root'));

引入所需的React库:

<!DOCTYPE html>
<html>
  <body>
    <div id="root"></div>
    <script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
    <script src="index.js"></script>
  </body>
</html>

运行以上代码,我们会发现控制台打印信息如下图所示:

从图中可以看出,type就是标签名,其他字段比较常用的有keyref以及props,其中props中会包含classNamestylechildren等字段。这些信息最终会映射成真实的DOM结点,所以这就是我们熟知的Virtual DOM,而ReactDOM.render()函数就是将虚拟DOM转换成真实DOM的工具。

我们现在可以得出一个结论,React.createElement()负责根据代码生成虚拟DOM,ReactDOM.render()负责将虚拟DOM映射到真实DOM上。

究竟React.createElement()ReactDOM.render()是如何将程序转换成真实DOM的呢?接下来,我们就来试着实现React.createElement()和ReactDOM.render()的逻辑,模拟一下这个过程。

先来实现React.createElement()方法:

const React = {
  // 创建DOM描述对象 即虚拟DOM
  createElement(tag, attrs, ...children) {
    let vnode = {
      type: tag,
      props: {
        ...attrs,
        children,
      }
    };

    return vnode;
  }
};

以上代码会生成下面的虚拟DOM结构:

然后是ReactDOM.render()方法:

const ReactDOM = {
  // 渲染真实DOM
  render(vnode, container) {
    let realDOM = this.generateDOM(vnode);
    container.appendChild(realDOM);
  },
  // 获取真实DOM
  generateDOM(vnode) {
    let elem = document.createElement(vnode.type);
    // 特殊key值映射
    let specialKeyMap = {
      className: 'class',
      fontSize: 'font-size',
    };
    let {props} = vnode;

    // 设置DOM属性
    props && Object.keys(props).forEach(key => {
      if (key === 'children') {
        // 处理子结点
        props.children.forEach(child => {
          if (typeof child === 'string') {
            // 纯内容结点
            elem.appendChild(document.createTextNode(child));
          } else {
            // DOM结点
            elem.appendChild(this.generateDOM(child));
          }
        });
      } else if (key === 'style') {
        // 设置样式属性
        let styleObj = props.style;
        let styleItems = [];

        Object.keys(styleObj).forEach(styleKey => {
          styleItems.push(`${specialKeyMap[styleKey] || styleKey}:${styleObj[styleKey]}`);
        });

        elem.setAttribute('style', styleItems.join(';'));
      } else {
        // 设置其他属性
        elem.setAttribute(specialKeyMap[key] || key, props[key]);
      }
    });

    return elem;
  }
};

最后我们把前面引用的React库替换成上面我们自己实现的代码,然后运行,见证奇迹的时刻到了:

只需两段简短的代码,我们就生成了一个迷你版的虚拟DOM,并最终生成了真实的DOM结构,是不是很简单?当然,React所实现的功能远不止这些,我们后续会继续介绍。

React系列文章:JSX生成真实DOM结点的更多相关文章

  1. React: 无状态组件生成真实DOM结点

    在上一篇文章中,我们总结并模拟了 JSX 生成真实 DOM 结点的过程,今天接着来介绍一下无状态组件的生成过程. 先以下面一段简单的代码举例: const Greeting = function ({ ...

  2. React: 有状态组件生成真实DOM结点

    上次我们分析了无状态组件生成 DOM 的过程,无状态组件其实就是纯函数,它不维护内部的状态,只是根据外部输入,输出一份视图数据.而今天我们介绍的有状态组件,它有内部的状态,因此在组件的内部,可以自行对 ...

  3. React: JSX生成真实DOM结点

    在上一篇文章中,我们介绍了 Babel 是如何将 JSX 代码编译成可执行代码的,随后也实现了一个自己的解析器,模拟了 Babel 编译的过程. 现在我们再来回顾一下,假定有如下业务代码: const ...

  4. React系列文章:无状态组件生成真实DOM结点

    在上一篇文章中,我们总结并模拟了JSX生成真实DOM结点的过程,今天接着来介绍一下无状态组件的生成过程. 先以下面一段简单的代码举例: const Greeting = function ({name ...

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

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

  6. React系列文章:Babel编译JSX生成代码

    上次我们总结了React代码构建后的Webpack模块组织关系,今天来介绍一下Babel编译JSX生成目标代码的一些规则,并且模拟整个生成的过程. 我们还是拿最简单的代码举例: import {gre ...

  7. React系列文章:Webpack模块组织关系

    现代前端开发离不开打包工具,以Webpack为代表的打包工具已经成为日常开发必备之利器,拿React技术栈为例,我们ES6形式的源代码,需要经过Webpack和Babel处理,才能生成发布版文件,在浏 ...

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

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

  9. React 系列文章(1): npm 手动搭建React 运行实例 (新手必看)

    摘 要 刚接触React 开发, 在摸索中构建react 运行环境,总会遇到各种坑:本文,将用最短时间解决webpack+react 环境搭建问题. 1.如果你还没有React基础 看这里. 2.如果 ...

随机推荐

  1. python的技巧和方法你了解多少?

    学了这些你的python代码将会改善与你的技巧将会提高. 1. 路径操作 比起os模块的path方法,python3标准库的pathlib模块的Path处理起路径更加的容易. 获取当前文件路径 前提导 ...

  2. motor的使用

    # -*- coding: utf-8 -*- # @Time : 2018/11/18 10:41 PM # @Author : cxa # @File : motordb.py # @Softwa ...

  3. 转载:分布式文件系统 - FastDFS 在 CentOS 下配置安装部署(1)

    原文:http://blog.mayongfa.cn/192.html 一.安装 libfastcommon 和 FastDFS 1.下载安装 libfastcommon ,这里是通过wget下载(我 ...

  4. mac安装RabbitMQ

    1 下载 地址 http://www.rabbitmq.com/install-standalone-mac.html 2 rabbitmq的安装目录: /Users/ysyc1/rabbitmq_s ...

  5. abstract class 和 interface 区别

    本文出自与:heipai:tsg666 含有 abstract 修饰符的 class 即为抽象类,abstract 类不能创建的实例对象.含有 abstract 方法的类必须定义为 abstract ...

  6. HTTP协议 (1)

    HTTP协议是Hyper Text Transfer Protocol(超文本传输协议)的缩写,是用于从万维网(WWW:World Wide Web )服务器传输超文本到本地浏览器的传送协议. HTT ...

  7. php和NodeJs共存的开发环境

    1 折腾 php nodejs 到一起 nodejs当然很火,就像着火了一样,但是必须承认要搭建一个前端的demo开发环境还是PHP靠谱, windows下可以非常的集成套件,比如http://www ...

  8. linux java报错汇总

    一:♦linux 下javac 编译报 需要class, interface 或enum错误   ♦解析时已到达文件结尾 原因:大括号补匹配  //注意看报警提示  

  9. 正确停止线程的方式三 使用Thread类中的内置的中断标记位-----------不熟悉

    package charpter10; public class Processor implements Runnable { @Override public void run() { for ( ...

  10. [九省联考2018]一双木棋chess

    题解: 水题吧 首先很显然的是状压或者搜索 考虑一下能不能状压吧 这个东西一定是长成三角形的样子的 所以是可以状压的 相邻两位之间有几个0代表他们差几 这样最多会有2n 然后就可以转移了 由于之前对博 ...