资讯专栏INFORMATION COLUMN

图解 React Virtual DOM

Soarkey / 3379人阅读

摘要:判断是否是有效的元素。主要和同构相关。是真实的模拟,真实是由真实的元素构成,也是由虚拟的元素构成。当这些对象上的数据发生变化时,通过打把变化同步到真实的上去。原创新书移动前端高效开发实战已在亚马逊京东当当开售。

作者: 阿希 (沪江Web前端开发工程师)
本文原创,转载请注明作者及出处。

了解 React 的人几乎都听过说 Virtual DOM,甚至不了解 React 的人也听过 Virtual DOM。那么 React 的 Virtual DOM 到底长什么样子呢?今天我们将一探 React 的源码来揭开 React Virtual DOM 的神秘面纱。

参考源码为React稳定版,版本号v15.4.1。

1. React

我们首先试着在控制台打印一下 React 看看会是什么样子:

从控制台看来,React是一个对象,那接下来我们找到相应的源码来确认看看(src/isomorphic/React.js):

var React = {
  Children: {
    map: ReactChildren.map,
    forEach: ReactChildren.forEach,
    count: ReactChildren.count,
    toArray: ReactChildren.toArray,
    only: onlyChild,
  },
  Component: ReactComponent,
  PureComponent: ReactPureComponent,
  createElement: createElement,
  cloneElement: cloneElement,
  isValidElement: ReactElement.isValidElement,
  PropTypes: ReactPropTypes,
  createClass: ReactClass.createClass,
  createFactory: createFactory,
  createMixin: function(mixin) {
    return mixin;
  },
  DOM: ReactDOMFactories,
  version: ReactVersion,
  __spread: __spread,
};

可以了解到,React 确实是一个 Object ,我们可以把 React 对象画成下图的形式,方便大家直观的观察:

React 是一个对象,里面包含了许多方法和属性,有最新的 v15 版本的方法,也有些以前的 API 和一些已经废弃不建议使用的 API。

Component 用来创建 React 组件类。

PureComponent 用来创建 React 纯组件类。

createElement 创建 React 元素。

cloneElement 拷贝 React 元素。

isValidElement 判断是否是有效的 React 元素。

PropTypes 定义 React props 类型。(过时的API)

createClass 创建 React 组件类(过时的API)。

createFactory 创建 React 工厂函数。(不建议使用)。

createMixin 创建 Mixin。

DOM 主要和同构相关。

version 当前使用的 React 版本号。

__spread 已废弃,直接用 Object.assign() 代替

__spread 方法已经废弃,不再建议使用。在作者写这篇文章的时候,React 又发布了 v15.5.0 版本,在这个版本里,createClassPropTypes 也已经被标记为过时的 API,会提示 warning。

对于原来的旧 API React.createClass,现在推荐开发者用 class 的方式继承 Component 或者 PureComponent

对于 PropTypes 的引入方式也不是原来的 import { PropTypes } from "react",而变成了 import PropTypes from "prop-types"

其他属性和方法我们暂且就不详细的讲述了,这篇文章就只详细的研究一下和创建 React Virtual DOM 最紧密相关的方法——React.createElement

React.createElement 方法其实是调用的ReactElement模块的 ReactElement.createElement 方法。

2. React Element

Virtual DOM 是真实 DOM 的模拟,真实 DOM 是由真实的 DOM 元素构成,Virtual DOM 也是由虚拟的 DOM 元素构成。真实 DOM 元素我们已经很熟悉了,它们都是 HTML 元素(HTML Element)。那虚拟 DOM 元素是什么呢?React 给虚拟 DOM 元素取名叫 React 元素(React Element)。

我们知道,React 可以通过组合一些 HTML 原生元素形成组件,然后组件又可以被其他的组件复用。所以,原生元素和组件其实在概念上都是一致的,都是具有特定功能和 UI 的可复用的元素。因此,React 把这些元素抽象成了 React Element。不论是 HTML 原生元素,例如:

,等。或者这些原生元素的组合(组件),例如 等。它们都是 React Element,而创建这些 Element 的方法就是 React.createElement

React Virtual DOM 就是由 React Element 构成的一棵树

接下来我们就探究下 React Element 到底长什么样以及 React 是如何创建这些 React Element 的。

2.1 ReactElement 模块

我们在控制台里直接打印出

hello

我们再打印出 ,App 组件的结构如下:

App

Hello world!

打印出的结果如下:

可以很直观的发现,打印的 HTML 元素并不是真实的 DOM 元素,打印的组件也不是 DOM 元素的集合,所有打印出来的元素都是一个对象,而且它们长的非常相似,那其实这些对象都是 React Element 对象。

然后我们再看看源码部分:

var ReactElement = function(type, key, ref, self, source, owner, props) {
  var element = {
    $$typeof: REACT_ELEMENT_TYPE,
    type: type,
    key: key,
    ref: ref,
    props: props,
    _owner: owner,
  };
  if (__DEV__) {
    // ...
  }
  return element;
};

ReactElement其实是一个工厂函数,接受7个参数,最终返回一个React Element对象。

$$type React Element 的标志,是一个Symbol类型。

type React 元素的类型。

key React 元素的 key,diff 算法会用到。

ref React 元素的 ref 属性,当 React 元素生成实际 DOM 后,返回 DOM 的引用。

props React 元素的属性,是一个对象。

_owner 负责创建这个 React 元素的组件。

参数中的 selfsource 都是只供开发环境下用的参数。从上面的例子我们可以发现唯一不同的就是type 了,对于原生元素,type 是一个字符串类型,记录了原生元素的类型;对于 react 组件来说呢,type 是一个构造函数,或者说它是一个类,记录了这个 react 组件的是哪一个类的实例。所以.type === App 的。

所以,每一个包装过后的React元素都是这样的对象:

{
    $$typeof: REACT_ELEMENT_TYPE,
    type: type,
    key: key,
    ref: ref,
    props: props,
    _owner: owner,
}

用图片表示 React Element,就是下图这样:

2.2 ReactElement.createElement 方法

在此之前,可能有人会问,我们开发当中似乎没有用到 React.createElement 方法呀。其实不然,看下面的示例:

class OriginalElement extends Component {
  render() {
    return (
      
Original Element div
); } }

经过babel转译之后是这样的

_createClass(OriginalElement, [{
    key: "render",
    value: function render() {
      return React.createElement(
        "div",
        null,
        "Original Element div"
      );
    }
  }]);

可以看到,所有的 JSX 都会被编译成 React.createElement 方法,所以这个方法可能是我们在使用React用的最多的方法。

接下来我们看看 React.createElement 方法是怎样的,前面说过了 React.createElement 方法其实就是 ReactElement.createElement 方法。

ReactElement.createElement = function(type, config, children) {
  var propName;
  var props = {};
  var key = null;
  var ref = null;
  var self = null;
  var source = null;
  if (config != null) {
    if (hasValidRef(config)) {
      ref = config.ref;
    }
    if (hasValidKey(config)) {
      key = "" + config.key;
    }
    self = config.__self === undefined ? null : config.__self;
    source = config.__source === undefined ? null : config.__source;

    for (propName in config) {
      if (hasOwnProperty.call(config, propName) &&
          !RESERVED_PROPS.hasOwnProperty(propName)) {
        props[propName] = config[propName];
      }
    }
  }
  var childrenLength = arguments.length - 2;
  if (childrenLength === 1) {
    props.children = children;
  } else if (childrenLength > 1) {
    var childArray = Array(childrenLength);
    for (var i = 0; i < childrenLength; i++) {
      childArray[i] = arguments[i + 2];
    }
    if (__DEV__) {
      // ...
    }
    props.children = childArray;
  }
  if (type && type.defaultProps) {
    var defaultProps = type.defaultProps;
    for (propName in defaultProps) {
      if (props[propName] === undefined) {
        props[propName] = defaultProps[propName];
      }
    }
  }
  if (__DEV__) {
    // ...
  }
  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props
  );
};

reactElement.createElement大致做了2件事。

第一件是初始化 React Element 里的各种参数,例如 typepropschildren 等。在初始化的时候,会提取出 keyref 这两个属性,然后 __self,__source 这两个属性也是仅开发用。所以如果你在组件里定义了 keyref__self__source 这4个属性中的任何一个,都是不能在 this.props 里访问到的。从第三个参数开始,传入的参数都会合并为 children 属性,如果只有一个,那么 children 就是第三个元素,如果超过一个,那么这些元素就会合并成一个 children 数组。

第二件是初始化 defaultProps,我们可以发现,defaultProps 是通过 type 来初始化的,我们在上面也说过,对于 react 组件来说,type 是 React Element 所属的类,所以可以通过 type 取到该类的 defaultProps(默认属性)。这里还有一点需要注意,如果我们把某个属性的值定义成 undefined,那么这个属性也会使用默认属性,但是定义成 null 就不会使用默认属性。

下面是图解:

4. 创建Virtual DOM树

有了上面的作为基础,那创建 Virtual DOM 就很简单了。整个 Virtual DOM 就是一个巨大的对象。

比如我们有这么一个 App

App:
Header:
List:
  • text 1
  • text 2
  • text 3
Logo:

text logo

ReactDOM.render(, document.getElementById("root"))

通过上面的了解到的 React Element 创建方式,我们不难知道,生成的对应的 Virtual DOM 应该是类似于这样的:

需要注意的是,这些元素并不是真实的 DOM 元素, 它们只是一些对象,而且我们可以看到 React 组件实际上是概念上的形态,最终还是会生成原生的虚拟 DOM 对象。当这些对象上的数据发生变化时,通过打 patch 把变化同步到真实的 DOM 上去。

目前我们可以认为 Virtual DOM 就是这样的一种形态,但是实际上,并没有这么简单,这只是最基本的样子,在后续的文章中我会带大家一起看看更高级的形态。


iKcamp原创新书《移动Web前端高效开发实战》已在亚马逊、京东、当当开售。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/85052.html

相关文章

  • React / Vue 项目时为什么要在列表组件中写 key,其作用是什么

    摘要:当正在更新使用渲染的元素列表时,它默认使用就地更新的策略。如果数据项的顺序被改变,将不会移动。避免对节点就地复用需要修改的节点位置没有改变,是内容更新了,这虽然提高了复用性能,但是往往在复杂的表单会导致状态出现错位。 当 Vue 正在更新使用 v-for 渲染的元素列表时,它默认使用就地更新的策略。如果数据项的顺序被改变,Vue 将不会移动 DOM。 元素来匹配数据项的顺序,而是就地更...

    eccozhou 评论0 收藏0
  • 一起理解 Virtual DOM

    摘要:具体而言,就是每次数据发生变化,就重新执行一次整体渲染。而给出了解决方案,就是。由于只关注,通过阅读两个库的源码,对于的定位有了更深一步的理解。第二个而且,技术本身不是目的,能够更好地解决问题才是王道嘛。 前言 React 好像已经火了很久很久,以致于我们对于 Virtual DOM 这个词都已经很熟悉了,网上也有非常多的介绍 React、Virtual DOM 的文章。但是直到前不久...

    Tangpj 评论0 收藏0
  • React Virtual DOM 理解

    摘要:二原理每个都有两个,一个是新的,一个是原来的。三实现过程四算法的理解与实现本质上就是在和之间做了一个缓存。将差异的应用到真正的树上对真实上的树进行深度优先遍历,在所有的差异列表中找出当前遍历的节点差异,然后根据不同进行操作。 React Virtual DOM 一、概念 在react中,对于每个DOM对象都有一个相应的虚拟DOM对象,相当于DOM对象的轻量级副本 由于是Virtual...

    smallStone 评论0 收藏0
  • 解读React源码(一):初探React源码

    摘要:前言的基本概念组件的构建方法以及高级用法这背后的一切如何运转深入内部的实现机制和原理初探源码代码组织结构包含一系列的工具方法插件包含一系列同构方法包含一些公用或常用方法如等包含一些测试方法等包含一些边界错误的测试用例是代码的核心部分它包含了 前言 React的基本概念,API,组件的构建方法以及高级用法,这背后的一切如何运转,深入Virtual DOM内部的实现机制和原理. 初探Rea...

    Eminjannn 评论0 收藏0
  • 解读React源码(二):Virtual DOM模型

    摘要:模型模型负责底层框架的构建工作它拥有一整套的标签并负责虚拟节点及其属性的构建更新删除等工作其实构建一套简易模型并不复杂它只需要具备一个标签所需的基本元素即可标签名属性样式子节点唯一标识中的节点称为它分为种类型其中又分为和创建元素输入输出通过 Virtual DOM模型 1.Virtual DOM模型负责Virtual DOM底层框架的构建工作,它拥有一整套的Virtual DOM标签,...

    kuangcaibao 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<