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 源碼

相關文章
相關標籤/搜索