React學(xué)習(xí)之---JSX與虛擬DOM

? ?? ?最近開始了React的學(xué)習(xí)之旅,感覺這些框架都是一個(gè)套路莲组。早先有學(xué)過VUE诊胞,它就像是在寫模板,大部分是HTML锹杈。而React是在寫模塊撵孤,大部分是寫js。

? ?? ?React中有一個(gè)神奇的技術(shù)就是JSX竭望,VUE2.0中引入也了React的JSX技術(shù)邪码,但是React為什么要使用JSX技術(shù)呢?我們先來看官方的觀點(diǎn):

JSX is not required to use React, but itmakes code more readable, and writing it feels like writing HTML.

? ?? ?React并不是必須使用JSX咬清,但是JSX可以增加代碼的可讀性闭专,并且寫起來就像寫HTML一樣。

? ?? ?JSX為什么可以增加代碼的可讀性呢?
??其實(shí)是這樣的旧烧。React中有一個(gè)核心的機(jī)制影钉,虛擬DOM。虛擬DOM是一個(gè)原生的JavaScript對象掘剪,并且具有真實(shí)DOM信息的一些屬性平委。它在APP與DOM之間建立了一個(gè)抽象層,當(dāng)數(shù)據(jù)和狀態(tài)發(fā)生改變時(shí)杖小,只需要在虛擬DOM上進(jìn)行操作肆汹,最后再同步到真實(shí)的DOM中。由此可見予权,虛擬DOM可以減少直接操作DOM的次數(shù)昂勉,減少不必要的[repaint和reflow],提高性能扫腺。
??虛擬DOM是如何更新的呢岗照?
??當(dāng)狀態(tài)和數(shù)據(jù)發(fā)生變化時(shí),React會(huì)生成新的虛擬DOM樹笆环,然后將新的虛擬DOM樹與舊虛擬DOM樹進(jìn)行對比攒至,這用到了diff算法。diff算法的核心是對樹進(jìn)行分層比較躁劣,并且只對兩棵樹的同層進(jìn)行比較迫吐。React會(huì)對新舊兩棵樹進(jìn)行一個(gè)深度優(yōu)先的遍歷,每遍歷到一個(gè)節(jié)點(diǎn)就把該節(jié)點(diǎn)和新的樹進(jìn)行對比账忘。當(dāng)發(fā)現(xiàn)節(jié)點(diǎn)已經(jīng)不存在志膀,則該節(jié)點(diǎn)及其子節(jié)點(diǎn)會(huì)被完全刪除掉熙宇,不會(huì)用于進(jìn)一步的比較。這樣只需要對樹進(jìn)行一次遍歷溉浙,便能完成整個(gè)DOM樹的比較烫止。

image.png

??如果節(jié)點(diǎn)發(fā)生了跨級移動(dòng),則被移動(dòng)的節(jié)點(diǎn)會(huì)直接被銷毀戳稽,并且會(huì)創(chuàng)建新節(jié)點(diǎn)掛載到馆蠕,目標(biāo)DOM上。由此可見惊奇,在編寫代碼是要加強(qiáng)DOM結(jié)構(gòu)的穩(wěn)定性互躬,如果有必要更改,可以利用CSS來隱藏或顯示某些節(jié)點(diǎn)颂郎,而不是真的移除或添加DOM節(jié)點(diǎn)吨铸。其實(shí)一旦接受了React的寫法,就會(huì)發(fā)現(xiàn)前面所說的那種移動(dòng)的寫法幾乎不會(huì)被考慮祖秒,這里可以說是React限制了某些寫法诞吱,不過遵守這些實(shí)踐確實(shí)會(huì)使得React有更好的渲染性能。
??在React源碼中竭缝,diff算法的核心部分為_updateChildren方法房维,它是ReactMultiChild.js內(nèi)部的一個(gè)方法。

_updateChildren: function (nextNestedChildrenElements, transaction, context) {
      var prevChildren = this._renderedChildren;
      var removedNodes = {};
      var mountImages = [];
      var nextChildren = this._reconcilerUpdateChildren(prevChildren, nextNestedChildrenElements, mountImages, removedNodes, transaction, context);
      if (!nextChildren && !prevChildren) {
        return;
      }
      var updates = null;
      var name;
      // `nextIndex` will increment for each child in `nextChildren`, but
      // `lastIndex` will be the last index visited in `prevChildren`.
      var nextIndex = 0;
      var lastIndex = 0;
      // `nextMountIndex` will increment for each newly mounted child.
      var nextMountIndex = 0;
      var lastPlacedNode = null;
      for (name in nextChildren) {
        if (!nextChildren.hasOwnProperty(name)) {
          continue;
        }
        var prevChild = prevChildren && prevChildren[name];
        var nextChild = nextChildren[name];
        if (prevChild === nextChild) {
           // 同一個(gè)引用抬纸,說明是使用的同一個(gè)component,所以我們需要移動(dòng)已有的子節(jié)點(diǎn) 
          updates = enqueue(updates, this.moveChild(prevChild, lastPlacedNode, nextIndex, lastIndex));
          lastIndex = Math.max(prevChild._mountIndex, lastIndex);
          prevChild._mountIndex = nextIndex;
        } else {
          if (prevChild) {
            // Update `lastIndex` before `_mountIndex` gets unset by unmounting.
            lastIndex = Math.max(prevChild._mountIndex, lastIndex);
            // The `removedNodes` loop below will actually remove the child.
          }
          // The child must be instantiated before it's mounted.
          updates = enqueue(updates, this._mountChildAtIndex(nextChild, mountImages[nextMountIndex], lastPlacedNode, nextIndex, transaction, context));
          nextMountIndex++;
        }
        nextIndex++;
        lastPlacedNode = ReactReconciler.getHostNode(nextChild);
      }
      // Remove children that are no longer present.
      for (name in removedNodes) {
        if (removedNodes.hasOwnProperty(name)) {
          // 添加新的子節(jié)點(diǎn)在指定的位置上 
          updates = enqueue(updates, this._unmountChild(prevChildren[name], removedNodes[name]));
        }
      }
      if (updates) {
        processQueue(this, updates);
      }
      this._renderedChildren = nextChildren;

      if (process.env.NODE_ENV !== 'production') {
        setChildrenForInstrumentation.call(this, nextChildren);
      }
    }

關(guān)于react的渲染和更新機(jī)制的具體操作將在下一篇文章中介紹咙俩,現(xiàn)在我們回歸最前面的問題,JSX在React中有什么作用呢湿故?
??如果不使用JSX阿趁,官方提供了React.createElement()機(jī)制,如下所示:

var slider = React.createElement(
  'div',
  {className: 'sidebar'},
  null
)

我們來看一看React.createElement的源碼:

ReactElement.createElement = function (type, config, children) {
  var propName;
  //初始化
  var props = {};
  var key = null;
  var ref = null;
  var self = null;
  var source = null;
  // 提取參數(shù)
  if (config != null) {
    ref = config.ref === undefined ? null : config.ref;
    key = config.key === undefined ? null : '' + config.key;
    self = config.__self === undefined ? null : config.__self;
    source = config.__source === undefined ? null : config.__source;
    // 提取出config中的prop坛猪,并進(jìn)行存儲(chǔ)
    for (propName in config) {
      if (config.hasOwnProperty(propName) && !RESERVED_PROPS.hasOwnProperty(propName)) {
        props[propName] = config[propName];
      }
    }
  }
  // 提取child信息
  var childrenLength = arguments.length - 2;
  if (childrenLength === 1) {
    // only one child
    props.children = children;
  } else if (childrenLength > 1) {
    // multiple child
    var childArray = Array(childrenLength);
    for (var i = 0; i < childrenLength; i++) {
      childArray[i] = arguments[i + 2];
    }
    props.children = childArray;
  }
  // 提取defaultProps脖阵,對于沒有的屬性設(shè)置默認(rèn)值
  if (type && type.defaultProps) {
    var defaultProps = type.defaultProps;
    for (propName in defaultProps) {
      if (props[propName] === undefined) {
        props[propName] = defaultProps[propName];
      }
    }
  }
  // 返回一個(gè)ReactElement對象
  return ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props);
};

ReactElement源碼:

var ReactElement = function (type, key, ref, self, source, owner, props) {
  var element = {
    // This tag allow us to uniquely identify this as a React Element
    $$typeof: REACT_ELEMENT_TYPE,
    type: type,
    key: key,
    ref: ref,
    props: props,
    // Record the component responsible for creating this element.
    _owner: owner
  };
    return element;
}

由此可見,一個(gè) ReactElement 實(shí)例具有DOM的4個(gè)必要屬性墅茉,分別是type命黔、key、ref就斤、props悍募。并且它沒有方法,原型也沒有任何屬性和方法洋机。ReacrtElement的實(shí)例對象就是我們前面所說的虛擬的DOM對象坠宴。
??因此,完全可以用JavaScript構(gòu)建完整的界面DOM樹绷旗,就像可以用JavaScript創(chuàng)建真實(shí)DOM喜鼓。但這樣的代碼可讀性并不好忧设,于是就出現(xiàn)了JSX。它是javaScript的擴(kuò)展颠通,并且提供了語義化標(biāo)簽,使用熟悉的語法定義HTML樹膀懈,并且程序結(jié)構(gòu)更加直觀化顿锰。將上面的代碼用JSX改寫后如下:

var slider =∑袈А(<div className="sidebar" />)

XML語法直接加入到JavaScript代碼中硼控,讓你能夠高效的通過代碼而不是模板來定義界面。之后JSX通過翻譯器轉(zhuǎn)換到純JavaScript再由瀏覽器執(zhí)行胳赌±魏常可以使用react-tools和babel將將jsx轉(zhuǎn)碼成js,但是前者已經(jīng)被放棄了疑苫,下面我們就介紹babel的轉(zhuǎn)碼原理熏版。
??babel是一個(gè)轉(zhuǎn)碼器,但是它更像是一個(gè)編譯器捍掺,將源代碼編譯成適合開發(fā)環(huán)境的另一種語言代碼撼短。我們知道編譯過程一般有5個(gè)階段:詞法分析;語法分析挺勿;語義分析與中間代碼產(chǎn)生曲横;優(yōu)化;目標(biāo)代碼生成不瓶。
??詞法分析是以詞法規(guī)則為依據(jù)對輸入的源程序進(jìn)行單詞及其屬性的識別禾嫉,識別出一個(gè)個(gè)單詞符號;
??語法分析是根據(jù)語言的語法規(guī)則蚊丐,把單詞流組成各類語法單位熙参,如:短語、句子麦备、過程尊惰、程序。
??語義分析是將檢查程序的語義正確性泥兰,以保證程序各部分能有意義的結(jié)合在一起弄屡,為以后的代碼生成階段收集類型信息;
??中間代碼是不依賴于機(jī)器但是又便于生成依賴于機(jī)器的目標(biāo)代碼的一種結(jié)構(gòu)簡單鞋诗、含義明確的記號系統(tǒng)膀捷;
??代碼優(yōu)化是對前面產(chǎn)生的中間代碼進(jìn)行加工變換,以期在最后階段能產(chǎn)生更為高效的目標(biāo)代碼削彬;
??目標(biāo)代碼生成是把經(jīng)過優(yōu)化的中間代碼轉(zhuǎn)化成特定 機(jī)器上的低級語言代碼全庸。
??babel的工作過程分為三個(gè)階段:
??Step1:解析秀仲,將代碼字符串解析成抽象語法樹;
??Step2:遍歷做轉(zhuǎn)換壶笼,遍歷抽象語法樹神僵,并將需要變化的地方直接在該語法樹對象上做修改;
??Step3:生成新代碼覆劈,將變換后的新抽象語法樹生成對應(yīng)的字符串代碼保礼。
??其中,在第Step1解析階段包含詞法分析责语、語法分析和語義分析炮障,驗(yàn)證語法和語義的正確性。同時(shí)坤候,由字符串代碼生成抽象語法樹胁赢,它是源代碼抽象語法結(jié)構(gòu)的樹狀表現(xiàn)形式。
??總結(jié)
??React中引入了虛擬DOM白筹,它是一個(gè)原生的JavaScript對象智末,相對于DOM對象來說更易于處理和操作。為了使代碼結(jié)構(gòu)更加清晰徒河,React又引入了JSX語法吹害,它是JavaScript擴(kuò)展語法,同時(shí)也是帶屬性的樹狀結(jié)構(gòu)語法虚青,增加了代碼的可讀性它呀。使用JSX語法必須使用轉(zhuǎn)碼器,將JSX轉(zhuǎn)換為JS棒厘,代碼才可以在瀏覽器上執(zhí)行纵穿。

? ?? ?參考資料:

? ?? ?https://github.com/livoras/blog/issues/13

? ?? ?http://www.infoq.com/cn/articles/react-jsx-and-component

? ?? ?https://reactjs.org/docs/jsx-in-depth.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市奢人,隨后出現(xiàn)的幾起案子谓媒,更是在濱河造成了極大的恐慌,老刑警劉巖何乎,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件句惯,死亡現(xiàn)場離奇詭異,居然都是意外死亡支救,警方通過查閱死者的電腦和手機(jī)抢野,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來各墨,“玉大人指孤,你說我怎么就攤上這事。” “怎么了恃轩?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵结洼,是天一觀的道長。 經(jīng)常有香客問我叉跛,道長松忍,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任筷厘,我火速辦了婚禮鸣峭,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘敞掘。我一直安慰自己,他們只是感情好楣铁,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布玖雁。 她就那樣靜靜地躺著,像睡著了一般盖腕。 火紅的嫁衣襯著肌膚如雪赫冬。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天溃列,我揣著相機(jī)與錄音劲厌,去河邊找鬼。 笑死听隐,一個(gè)胖子當(dāng)著我的面吹牛补鼻,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播雅任,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼风范,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了沪么?” 一聲冷哼從身側(cè)響起硼婿,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎禽车,沒想到半個(gè)月后寇漫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡殉摔,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年州胳,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片逸月。...
    茶點(diǎn)故事閱讀 39,731評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡陋葡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出彻采,到底是詐尸還是另有隱情腐缤,我是刑警寧澤捌归,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站岭粤,受9級特大地震影響惜索,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜剃浇,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一巾兆、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧虎囚,春花似錦角塑、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蒲列,卻和暖如春窒朋,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蝗岖。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工侥猩, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人抵赢。 一個(gè)月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓欺劳,卻偏偏與公主長得像,于是被迫代替她去往敵國和親铅鲤。 傳聞我的和親對象是個(gè)殘疾皇子杰标,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評論 2 354

推薦閱讀更多精彩內(nèi)容