React中的Virtual DOM是什么

在讲解虚拟DOM(Virtual DOM)以前能够先了解下真实的DOM是什么html

Virtual DOM

Virtual DOM 本质上是JavaScript对象,是对真实DOM的的一种描述方式。
即JS对象模拟的DOM结构,将DOM变化的对比放在JS层来作。让UI渲染变成 UI = f(data)的形式。
示例:HTML里的一段标签文本前端

<ul id="list">
    <li class="item">Item1</li>
    <li class="item">Item2</li>
</ul>
复制代码

用虚拟DOM结构能够表示为:react

{
    tag: "ul",
    attrs: {
        id: "list"
    },
    children: [
        {
            tag: "li",
            attrs: { className: "item" },
            children: ["Item1"]
        }, {
            tag: "li",
            attrs: { className: "item" },
            children: ["Item2"]
        }
    ]
} 
复制代码

tag是标签名称
attrs是标签属性组成的对象
children是包含在标签内的子标签git

因此,Virtual DOM 实际上是一个JS对象,相似JSON格式,React中就是将JSX元素的名称、属性和内容做为对象及其属性来建立Virtual DOM。github

为何须要Virtual DOM

  • 跨平台。支持跨平台,能够将JS对象渲染到浏览器DOM之外的环境中,经过Virtual DOM能够实现服务器端渲染。好比在Node.js中没有DOM,能够借助Virtual DOM 来实现SSR。支持了跨平台开发,好比ReactNative。
  • 性能优化。前端性能优化的一个秘诀就是尽量少地操做DOM,优秀的Virtual DOM Diff 算法使得Virtual DOM将DOM的比对操做放在JS层,实现的在patch过程当中尽量地一次性将差别更新到DOM中,减小浏览器没必要要的重绘,提升效率。这样保证了DOM不会出现性能不好的状况。
  • 开发体验。咱们更新网上的展现内容是经过操纵HTML元素来实现的,要先获取到元素,而后再更新。虽然有jQuery框架来实现,可是仍是比较复杂的。React框架用Virtual DOM帮助开发者不须要面对复杂的api,声明式编写UI,专一于更改数据就好了。

不过,针对单一场景的性能优化,专门针对该场景的js进行极致优化 比 Virtual DOM 方案更适用。算法

React中的Virtual DOM

Virtual DOM 元素

在React开发中,Virtual DOM 元素便是React 元素,常常会使用JSX来写React元素,好比
简单的api

<div>Hello</div>
复制代码

稍微复杂一些的浏览器

<div className="divStyleName" onClick={(e) => console.log('点击了')}>
    Hello
    <img src="pic_url">img</img>
    <div>div2</div>
    <div></div>
</div>;
复制代码

不过,在React中不是必需要使用JSX的。
由于其实 JSX 元素只是调用 React.createElement(component, props, ...children) 的语法糖。其本质仍是JavaScript,全部的JSX语法最终都会被转换成对这个方法的调用。所以,使用 JSX 能够完成的任何事情均可以经过纯 JavaScript 完成。
同时,浏览器是不能直接读取JSX,为了使浏览器可以读取JSX,须要在项目构建环境中配置有关 JSX 编译。好比 让 Babel 这样的 JSX 转换器将 JSX语句 转换为 目标 js 代码,再将其传给浏览器,JS引擎才能成功执行语句。性能优化

用纯JavaScript来写上面的JSX示例(通过babel转译)服务器

React.createElement("div", {
    className: "divStyleName",
    onClick: e => console.log('点击了')
  }, "Hello", React.createElement("img", {
    src: "pic_url"
  }, "img"), React.createElement("div", null, "div2"), React.createElement("div", null));
复制代码

从最层的React.createElement分析起
第一个参数值是组件名
第二个参数值是一个对象,由组件的属性构成
第三到后面的一系列参数值,就是子元素了。若是子元素依然是JSX元素,就继续经过React.createElement来建立对象
React.createElement建立的React元素就是虚拟DOM树的元素,因而,就构成出一个虚拟DOM树了。

这里在浏览器窗口中看下上面所述内容的结果

注意这里的type的值是"div",是同名的原生 html 的div标签名

再来自定义一个组件,名为HelloComponent,JSX实现为

class HelloComponent extends React.Component {
  render() {
    return <div>Hello {this.props.toWhat}</div>;
  }
}

<HelloComponent toWhat="World" />
复制代码

Babel转译后对应的js代码为

class HelloComponent extends React.Component {
  render() {
    return React.createElement("div", null, "Hello ", this.props.toWhat);
  }
}

React.createElement(HelloComponent, {
    toWhat: "World"
  });

复制代码

而后在浏览器窗口看下

注意这里type的值是一个类构造函数了

这是babel在编译时会判断JSX中组件的首字母:

  • 当首字母为小写时,其被认定为同名的原生 html标签,createElement的第一个变量被编译为字符串
  • 当首字母为大写时,其被认定为自定义组件,createElement的第一个变量被编译为对象。

因此自定义组件名称必需要首字母大写。

下面就来研究下React.createElement(component, props, ...children)具体的实现 以及 是如何来构建Virtual DOM的吧~

Virtual DOM 实现原理

要开始看React 源码啦~

ReactElement

由于是React.createElement,因此天然就找到React.js文件,查看其createElement方法。 而后就发现其实的调用了ReactElement.js文件中的createElement方法,返回一个ReactElement对象

/** * Create and return a new ReactElement of the given type. * See https://reactjs.org/docs/react-api.html#createelement */
export function createElement(type, config, children) {
  let propName;

  // Reserved names are extracted
  const props = {};

  let key = null;
  let ref = null;
  let self = null;
  let source = null;

  /* ... 根据入参各类逻辑判断赋值ref、source、self、props等 ... */
  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props,
  );
}
复制代码

再看下ReactElement方法的具体实现

const ReactElement = function(type, key, ref, self, source, owner, props) {
  const element = {
    // This tag allows us to uniquely identify this as a React Element
    $$typeof: REACT_ELEMENT_TYPE,

    // Built-in properties that belong on the element
    type: type,
    key: key,
    ref: ref,
    props: props,

    // Record the component responsible for creating this element.
    _owner: owner,
  };

  if (__DEV__) {
    // 天然是给element的各个属性赋值了
  }

  return element;
};

复制代码

返回的是是一个JS对象,在React中称为ReactElement对象:

{
    $$typeof: REACT_ELEMENT_TYPE,
    type: type,
    key: key,
    ref: ref,
    props: props,
    _owner: owner,
}
复制代码

结构正是上面在浏览器窗口中看到的React.createElement的结果的结构。

这里简单介绍下ReactElement对象的属性及其含义。

  • <text>$$typeof: React 元素的标识,即"react.element",Symbol类型。
  • type: React 元素的类型,前面看到值能够是字符串,也能够是类构造函数。
  • props: React 元素的属性,是一个对象,由JSX的属性值和子元素构成。
  • key: React 元素的 key,Virtual DOM Diff 算法里会用到。
  • ref: React 元素的 ref 属性,当 React 元素生成真实的 DOM 元素后,返回 DOM 元素的引用。
  • _owner: 负责建立这个 React 元素的组件。

建立Virtual DOM树

了解了单个ReactElement对象后,前面提到的示例层层调用React.createElement,就能够建立出一个Virtual DOM Tree了,即生成原生的虚拟 DOM 对象。

React.createElement("div", {
    className: "divStyleName",
    onClick: e => console.log('点击了')
  }, "Hello", React.createElement("img", {
    src: "pic_url"
  }, "img"), React.createElement("div", null, "div2"), React.createElement("div", null));
复制代码

参考资料

在线Babel编辑器编写查看更多JSX转换成JavaScript的示例

图解 React Virtual DOM

React 源码

相关文章
相关标签/搜索