在上一篇文章中,我们总结并模拟了JSX生成真实DOM结点的过程,今天接着来介绍一下无状态组件的生成过程。

先以下面一段简单的代码举例:

const Greeting = function ({name}) {
  return <div>{`hello ${name}`}</div>;
};

const App = <Greeting name="scott"/>;

console.log(App);

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

可以看出,Greeting是一个无状态组件,我们来看看编译过后的可执行代码:

var Greeting = function Greeting(_ref) {
  var name = _ref.name;

  return React.createElement(
    "div",
    null,
    "hello " + name
  );
};

var App = React.createElement(Greeting, { name: "scott" });

console.log(App);

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

我们看到,调用Greeting组件时传入的name属性,出现在React.createElement()方法的第二个参数中,这和前面介绍的JSX是一致的,不同的是,React.createElement()方法的第一个参数不再是一个标签名,而是一个函数引用,指向了我们声明的Greeting组件,而name属性也作为参数的成员出现在组件内部,这个参数名为_ref,实则是我们熟知的props

下图是我们运行上面代码之后,打印出的App数据结构,即虚拟DOM结构:

我们再来看一个稍微复杂些的例子:

const Greeting = function ({name}) {
  return (
    <div>{`hello ${name}`}</div>
  );
};

const Container = function ({children}) {
  return (
    <div className="container">
      {children}
    </div>
  );
};

const App = (
  <Container>
    <Greeting name="scott"/>
    <Greeting name="jack"/>
    <Greeting name="john"/>
  </Container>
);

console.log(App);

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

在上面代码中,我们定义了两个无状态组件,其中Container用来作为外层的容器,Greeting则用来显示实际的业务视图。

现在再来看看编译后的代码结构:

var Greeting = function Greeting(_ref) {
  var name = _ref.name;

  return React.createElement(
    "div",
    null,
    "hello " + name
  );
};

var Container = function Container(_ref2) {
  var children = _ref2.children;

  return React.createElement(
    "div",
    { className: "container" },
    children
  );
};

var App = React.createElement(
  Container,
  null,
  React.createElement(Greeting, { name: "scott" }),
  React.createElement(Greeting, { name: "jack" }),
  React.createElement(Greeting, { name: "john" })
);

console.log(App);

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

这次我们主要观察Container的结构,它实际上是将React.createElement()方法的第三个参数作为props.children传递到了组件内部,而这个children是一个Greeting,最终是将Greeting渲染在Container组件内部。

接下来,我们要改进一下之前实现的React.createElement()ReactDOM.render()方法,使它们支持组件的形式,模拟生成虚拟DOM和真实DOM。

先来看看React.createElement()方法:

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

    // 组件参数的props.children本身是数组
    // 所以调用组件函数时这里需要特殊处理
    if (Array.isArray(children[0])) {
      propsChildren = children[0];
    }

    // 结点
    let vnode = {
      type,
      props: {
        ...props,
        children: propsChildren,
      }
    };

    // 挂载组件函数体的虚拟DOM
    if (typeof type === 'function') {
      vnode.body = type({
        ...props,
        children,
      });
    }

    return vnode;
  }
};

上面的代码主要对组件做了特殊处理。如果当前处理对象是组件,则对应的type就是函数的引用,我们会调用这个组件函数,然后将函数体的结果作为body属性挂载到该结点上。需要注意的是,我们在上面方法参数中使用了可变参数的形式,如果直接引用这个children,它本身就是一个变参数组,如果组件体内使用了props.children,那么在调用React.createElement()时,变参数组的形式将会是[[...]],所以我们需要特殊处理一下。

现在,我们运行程序,看看上面代码生成的虚拟DOM结构:

最后,再来看看ReactDOM.render()方法:

const ReactDOM = {
  // 渲染真实DOM
  render(vnode, container) {
    let realDOM = this.generateDOM(vnode);
    container.appendChild(realDOM);
  },
  // 获取真实DOM
  generateDOM(vnode) {
    if (typeof vnode.type === 'function') {
      // 将组件函数体的虚拟DOM生成真实DOM
      return this.generateDOM(vnode.body);
    }

    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;
  }
};

上面代码中改动较小,我们只添加了几行针对组件的处理逻辑,如果是组件函数,则将函数体的虚拟DOM生成真实DOM。

最后,我们来看看最终生成的DOM结构:

React系列文章:无状态组件生成真实DOM结点的更多相关文章

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

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

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

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

  3. react的redux无状态组件

    Provider功能主要为以下两点: 在原应用组件上包裹一层,使原来整个应用成为Provider的子组件 接收Redux的store作为props,通过context对象传递给子孙组件上的connec ...

  4. React系列文章:JSX生成真实DOM结点

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

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

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

  6. react 中的无状态函数式组件

    无状态函数式组件,顾名思义,无状态,也就是你无法使用State,也无法使用组件的生命周期方法,这就决定了函数组件都是展示性组件,接收Props,渲染DOM,而不关注其他逻辑. 其实无状态函数式组件也是 ...

  7. React 中的 Component、PureComponent、无状态组件 之间的比较

    React 中的 Component.PureComponent.无状态组件之间的比较 table th:first-of-type { width: 150px; } 组件类型 说明 React.c ...

  8. React中的高阶组件,无状态组件,PureComponent

    1. 高阶组件 React中的高阶组件是一个函数,不是一个组件. 函数的入参有一个React组件和一些参数,返回值是一个包装后的React组件.相当于将输入的React组件进行了一些增强.React的 ...

  9. 15. react UI组件和容器组件的拆分 及 无状态组件

    1.组件的拆分 组件拆分的前提 当所有的逻辑都出现在一个组件内时 组件会变得非常复杂 不便与代码的维护 所以对组件进行拆分 IU组件 进行页面渲染 容器组件  进行逻辑操作 UI组件的拆分 新建一个 ...

随机推荐

  1. 【.net】获取网页CDM的下载链接的地址

    using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServi ...

  2. Python3学习笔记07-List

    Python有6个序列的内置类型,但最常见的是列表和元 序列都可以进行的操作包括索引,切片,加,乘,检查成员. 此外,Python已经内置确定序列的长度以及确定最大和最小的元素的方法. 创建一个列表, ...

  3. 程序打jar包

    打包的时候,平常选择第二个选项 jar包中的内容为: 一般运行没有问题. 在部分情况,使用quartz时,在eclipse中不报错,使用第二种方式打包,运行会报错. 选择第一种打包方式,就不会报错了( ...

  4. 转载:Java中的字符串常量池详细介绍

    引用自:http://blog.csdn.net/langhong8/article/details/50938041 这篇文章主要介绍了Java中的字符串常量池详细介绍,JVM为了减少字符串对象的重 ...

  5. 石头剪刀布三局两胜(平局重来break用法)

  6. 如何用Python写一个贪吃蛇AI

    前言 这两天在网上看到一张让人涨姿势的图片,图片中展示的是贪吃蛇游戏, 估计大部分人都玩过.但如果仅仅是贪吃蛇游戏,那么它就没有什么让人涨姿势的地方了. 问题的关键在于,图片中的贪吃蛇真的很贪吃XD, ...

  7. PHP实现简单倒计时

    PHP实现倒计时代码示例如下: <?php $time1 = time(); $time2 = strtotime("2018-10-1"); $time3 = strtot ...

  8. python 全栈开发,Day84(django请求生命周期,FBV和CBV,ORM拾遗,Git)

    一.django 请求生命周期 流程图: 1. 当用户在浏览器中输入url时,浏览器会生成请求头和请求体发给服务端请求头和请求体中会包含浏览器的动作(action),这个动作通常为get或者post, ...

  9. 安装httpd过程,将网站部署到httpd过程

    1,配置DNSvi /etc/resolv.conf 加入以下代码 nameserver 192.168.0.1 nameserver 8.8.8.8 nameserver 8.8.4.4 2.输入y ...

  10. 这篇文章讲得精彩-深入理解 Python 异步编程(上)!

    可惜,二和三现在还没有出来~ ~~~~~~~~~~~~~~~~~~~~~~~~~ http://python.jobbole.com/88291/ ~~~~~~~~~~~~~~~~~~~~~~~~~~ ...