從React渲染流程分析Diff算法

一、什麼是虛擬DOM

在React中,render執行的結果獲得的並非真正的DOM節點,結果僅僅是輕量級的JavaScript對象,咱們稱之爲virtual DOM。
  • 簡單的說,其實所謂的virtual DOM就是JavaScript對象到Html DOM節點的映射;即便用JavaScript對象將Html結構表示出來,而這個對象就是virtual DOM。
  • eg:
  • Html:
<ul id='list'>
  <li class='item'>Item 1</li>
  <li class='item'>Item 2</li>
</ul>
  • JavaScript對象表示(virtual DOM)
{
  tagName: 'ul',
  props: {
    id: 'list'
  },
  children: [
    {tagName: 'li', props: {class: 'item'}, children: ["Item 1"]},
    {tagName: 'li', props: {class: 'item'}, children: ["Item 2"]},
  ]
}

二、何時會生成到virtual DOM

  • React生命週期擁有裝載、更新、卸載的三個階段;附上一張React生命週期圖

clipboard.png

  • 前面提到:render執行的結果獲得的並非真正的DOM節點,結果僅僅是輕量級的JavaScript對象,即在render函數調用時將會建立出虛擬DOM;
class Tab extends React.Component {
    render() {
        React.createElement(
          'p',
          { className: 'class'},
          'Hello React'
        )
    }
}

clipboard.png

  • 經過React.createElemen建立出虛擬DOM,而該函數只在Render函數中調用,因此在React裝載和更新的過程當中纔會有虛擬DOM的生成;至於掛載到真實DOM天然而然是ReactDom.render函數啦。

三、virtual DOM如何實現

  • 實現其實很簡單,主要是定義一個函數並把咱們傳進去的參數組成一個React元素對象,而type就是咱們傳進去的組件類型,能夠是一個類、函數或字符串(如'div')
  • React大體源碼:
function createElement(type, config, children) {
  let propName;

  const props = {};

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

  if (config != null) {
    if (hasValidRef(config)) {
      //  若是有ref,將它取出來
      ref = config.ref;
    }
    if (hasValidKey(config)) {
      //  若是有key,將它取出來
      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)
      ) {
        //  將除ref,key等這些特殊的屬性放到新的props對象裏
        props[propName] = config[propName];
      }
    }
  }

  //  獲取子元素
  const childrenLength = arguments.length - 2;
  if (childrenLength === 1) {
    props.children = children;
  } else if (childrenLength > 1) {
    const childArray = Array(childrenLength);
    for (let i = 0; i < childrenLength; i++) {
      childArray[i] = arguments[i + 2];
    }
    props.children = childArray;
  }

  //  添加默認props
  if (type && type.defaultProps) {
    const defaultProps = type.defaultProps;
    for (propName in defaultProps) {
      if (props[propName] === undefined) {
        props[propName] = defaultProps[propName];
      }
    }
  }
  
  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props,
  );
}

const ReactElement = function(type, key, ref, self, source, owner, props) {
  //  最終獲得的React元素
  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,
  };

  return element;
};
  • 打印出組件:

clipboard.png

四、爲何須要使用virtual DOM

  • DOM管理歷史階段:前端

    1. JS 或者 jQuery 操做 DOM: 當應用程序愈來愈複雜,須要在JS裏面維護的字段也愈來愈多,須要監聽事件和在事件回調用更新頁面的DOM操做也愈來愈多,應用程序會變得很是難維護。
    2. 後來產出 MVC、MVP 的架構模式,指望從代碼組織方式來下降維護難度。可是 MVC 架構並沒辦法減小維護的狀態,也沒有下降狀態更新時須要對頁面的更新操做,你須要操做的DOM仍是須要操做,只是換了個地方。
    3. 既然狀態改變了要操做相應的DOM元素,爲何不作一個東西讓視圖和狀態進行綁定,狀態變動了視圖自動變動。這就是後來人們想出了 MVVM 模式,只要在模版中聲明視圖組件是和什麼狀態進行綁定的,雙向綁定引擎就會在狀態更新的時候自動更新視圖;
    4. 但MVVM雙向數據綁定並非惟一的辦法,還有一個很是直觀的方法:一旦狀態發生了變化,就用模版引擎從新渲染整個視圖,而後用新的視圖更換掉舊的視圖。
  • React採用的就是第四種模式;可是咱們都知道對於操做DOM成本過高,而相對操做JavaScript就快速多了,而Html DOM能夠很簡單的用JavaScript對象表示出來(Virtual DOM就這樣誕生了)
  • 這樣的作法會致使不少的問題,最大的問題就是這樣作會很慢,由於即便一個小小的狀態變動都要從新構造整棵 DOM,性價比過低;而React Virtual DOM在狀態更新過程加了一些特別的操做來避免整棵 DOM 樹變動(它就是接下來的Diff算法)。
  • 接下來的Diff算法即將更新,敬請期待~~~
「積跬步、行千里」—— 持續更新中~,喜歡留下個贊哦!
相關文章
相關標籤/搜索