深入分析虛擬DOM的渲染原理和特性

導(dǎo)讀

React的虛擬DOMDiff算法是React的非常重要的核心特性容为,這部分源碼也非常復(fù)雜,理解這部分知識的原理對更深入的掌握React是非常必要的隔箍。

本來想將虛擬DOMDiff算法放到一篇文章谓娃,寫完虛擬DOM發(fā)現(xiàn)文章已經(jīng)很長了,所以本篇只分析虛擬DOM蜒滩。

本篇文章從源碼出發(fā)滨达,分析虛擬DOM的核心渲染原理(首次渲染),以及React對它做的性能優(yōu)化點(diǎn)俯艰。

說實(shí)話React源碼真的很難讀 ??捡遍,如果本篇文章幫助到了你,那么請給個(gè)贊 ?? 支持一下吧竹握。

開發(fā)中的常見問題

  • 為何必須引用React
  • 自定義的React組件為何必須大寫
  • React如何防止XSS
  • ReactDiff算法和其他的Diff算法有何區(qū)別
  • keyReact中的作用
  • 如何寫出高性能的React組件

如果你對上面幾個(gè)問題還存在疑問画株,說明你對React的虛擬DOM以及Diff算法實(shí)現(xiàn)原理還有所欠缺,那么請好好閱讀本篇文章吧。

首先我們來看看到底什么是虛擬DOM:

虛擬 DOM

在原生的JavaScript程序中谓传,我們直接對DOM進(jìn)行創(chuàng)建和更改蜈项,而DOM元素通過我們監(jiān)聽的事件和我們的應(yīng)用程序進(jìn)行通訊。

React會先將你的代碼轉(zhuǎn)換成一個(gè)JavaScript對象续挟,然后這個(gè)JavaScript對象再轉(zhuǎn)換成真實(shí)DOM紧卒。這個(gè)JavaScript對象就是所謂的虛擬DOM

比如下面一段html代碼:

<div class="title">
  <span>Hello ConardLi</span>
  <ul>
    <li>蘋果</li>
    <li>橘子</li>
  </ul>
</div>

React可能存儲為這樣的JS代碼:

const VitrualDom = {
  type: "div",
  props: { class: "title" },
  children: [
    {
      type: "span",
      children: "Hello ConardLi"
    },
    {
      type: "ul",
      children: [
        { type: "ul", children: "蘋果" },
        { type: "ul", children: "橘子" }
      ]
    }
  ]
};

當(dāng)我們需要?jiǎng)?chuàng)建或更新元素時(shí)诗祸,React首先會讓這個(gè)VitrualDom對象進(jìn)行創(chuàng)建和更改跑芳,然后再將VitrualDom對象渲染成真實(shí)DOM

當(dāng)我們需要對DOM進(jìn)行事件監(jiān)聽時(shí)直颅,首先對VitrualDom進(jìn)行事件監(jiān)聽博个,VitrualDom會代理原生的DOM事件從而做出響應(yīng)。

為何使用虛擬 DOM

React為何采用VitrualDom這種方案呢际乘?

提高開發(fā)效率

使用JavaScript坡倔,我們在編寫應(yīng)用程序時(shí)的關(guān)注點(diǎn)在于如何更新DOM

使用React脖含,你只需要告訴React你想讓視圖處于什么狀態(tài)罪塔,React則通過VitrualDom確保DOM與該狀態(tài)相匹配。你不必自己去完成屬性操作养葵、事件處理征堪、DOM更新,React會替你完成這一切关拒。

這讓我們更關(guān)注我們的業(yè)務(wù)邏輯而非DOM操作佃蚜,這一點(diǎn)即可大大提升我們的開發(fā)效率。

關(guān)于提升性能

很多文章說VitrualDom可以提升性能着绊,這一說法實(shí)際上是很片面的谐算。

直接操作DOM是非常耗費(fèi)性能的,這一點(diǎn)毋庸置疑归露。但是React使用VitrualDom也是無法避免操作DOM的洲脂。

如果是首次渲染,VitrualDom不具有任何優(yōu)勢剧包,甚至它要進(jìn)行更多的計(jì)算恐锦,消耗更多的內(nèi)存。

VitrualDom的優(yōu)勢在于ReactDiff算法和批處理策略疆液,React在頁面更新之前一铅,提前計(jì)算好了如何進(jìn)行更新和渲染DOM。實(shí)際上堕油,這個(gè)計(jì)算過程我們在直接操作DOM時(shí)潘飘,也是可以自己判斷和實(shí)現(xiàn)的肮之,但是一定會耗費(fèi)非常多的精力和時(shí)間,而且往往我們自己做的是不如React好的卜录。所以局骤,在這個(gè)過程中React幫助我們"提升了性能"。

所以暴凑,我更傾向于說,VitrualDom幫助我們提高了開發(fā)效率赘来,在重復(fù)渲染時(shí)它幫助我們計(jì)算如何更高效的更新现喳,而不是它比DOM操作更快。

如果您對本部分的分析有什么不同見解犬辰,歡迎在評論區(qū)拍磚嗦篱。

跨瀏覽器兼容

React基于VitrualDom自己實(shí)現(xiàn)了一套自己的事件機(jī)制,自己模擬了事件冒泡和捕獲的過程幌缝,采用了事件代理灸促,批量更新等方法,抹平了各個(gè)瀏覽器的事件兼容性問題涵卵。

跨平臺兼容

VitrualDomReact帶來了跨平臺渲染的能力浴栽。以React Native為例子。React根據(jù)VitrualDom畫出相應(yīng)平臺的ui層轿偎,只不過不同平臺畫的姿勢不同而已典鸡。

虛擬 DOM 實(shí)現(xiàn)原理

如果你不想看繁雜的源碼,或者現(xiàn)在沒有足夠時(shí)間坏晦,可以跳過這一章萝玷,直接 ??虛擬 DOM 原理總結(jié)

在上面的圖上我們繼續(xù)進(jìn)行擴(kuò)展,按照圖中的流程昆婿,我們依次來分析虛擬DOM的實(shí)現(xiàn)原理球碉。

JSX 和 createElement

我們在實(shí)現(xiàn)一個(gè)React組件時(shí)可以選擇兩種編碼方式,第一種是使用JSX編寫:

class Hello extends Component {
  render() {
    return <div>Hello ConardLi</div>;
  }
}

第二種是直接使用React.createElement編寫:

class Hello extends Component {
  render() {
    return React.createElement("div", null, `Hello ConardLi`);
  }
}

實(shí)際上仓蛆,上面兩種寫法是等價(jià)的睁冬,JSX只是為 React.createElement(component, props, ...children)方法提供的語法糖。也就是說所有的JSX代碼最后都會轉(zhuǎn)換成React.createElement(...)多律,Babel幫助我們完成了這個(gè)轉(zhuǎn)換的過程痴突。

如下面的JSX

<div>
  <img src="avatar.png" className="profile" />
  <Hello />
</div>

將會被Babel轉(zhuǎn)換為

React.createElement(
  "div",
  null,
  React.createElement("img", {
    src: "avatar.png",
    className: "profile"
  }),
  React.createElement(Hello, null)
);

注意,babel在編譯時(shí)會判斷JSX中組件的首字母狼荞,當(dāng)首字母為小寫時(shí)辽装,其被認(rèn)定為原生DOM標(biāo)簽,createElement的第一個(gè)變量被編譯為字符串相味;當(dāng)首字母為大寫時(shí)拾积,其被認(rèn)定為自定義組件,createElement的第一個(gè)變量被編譯為對象;

另外拓巧,由于JSX提前要被Babel編譯斯碌,所以JSX是不能在運(yùn)行時(shí)動態(tài)選擇類型的,比如下面的代碼:

function Story(props) {
  // Wrong! JSX type can't be an expression.
  return <components[props.storyType] story={props.story} />;
}

需要變成下面的寫法:

function Story(props) {
  // Correct! JSX type can be a capitalized variable.
  const SpecificStory = components[props.storyType];
  return <SpecificStory story={props.story} />;
}

所以肛度,使用JSX你需要安裝Babel插件babel-plugin-transform-react-jsx

{
    "plugins": [
        ["transform-react-jsx", {
            "pragma": "React.createElement"
        }]
    ]
}

創(chuàng)建虛擬 DOM

下面我們來看看虛擬DOM的真實(shí)模樣傻唾,將下面的JSX代碼在控制臺打印出來:

<div className="title">
  <span>Hello ConardLi</span>
  <ul>
    <li>蘋果</li>
    <li>橘子</li>
  </ul>
</div>

這個(gè)結(jié)構(gòu)和我們上面自己描繪的結(jié)構(gòu)很像,那么React是如何將我們的代碼轉(zhuǎn)換成這個(gè)結(jié)構(gòu)的呢承耿,下面我們來看看createElement函數(shù)的具體實(shí)現(xiàn)(文中的源碼經(jīng)過精簡)冠骄。

createElement函數(shù)內(nèi)部做的操作很簡單,將props和子元素進(jìn)行處理后返回一個(gè)ReactElement對象加袋,下面我們來逐一分析:

(1).處理 props:

  • 1.將特殊屬性ref凛辣、keyconfig中取出并賦值
  • 2.將特殊屬性selfsourceconfig中取出并賦值
  • 3.將除特殊屬性的其他屬性取出并賦值給props

后面的文章會詳細(xì)介紹這些特殊屬性的作用职烧。

(2).獲取子元素

  • 1.獲取子元素的個(gè)數(shù) —— 第二個(gè)參數(shù)后面的所有參數(shù)
  • 2.若只有一個(gè)子元素扁誓,賦值給props.children
  • 3.若有多個(gè)子元素,將子元素填充為一個(gè)數(shù)組賦值給props.children

(3).處理默認(rèn) props

  • 將組件的靜態(tài)屬性defaultProps定義的默認(rèn)props進(jìn)行賦值

ReactElement

ReactElement將傳入的幾個(gè)屬性進(jìn)行組合蚀之,并返回蝗敢。

  • type:元素的類型,可以是原生 html 類型(字符串)恬总,或者自定義組件(函數(shù)或class
  • key:組件的唯一標(biāo)識前普,用于Diff算法,下面會詳細(xì)介紹
  • ref:用于訪問原生dom節(jié)點(diǎn)
  • props:傳入組件的props
  • owner:當(dāng)前正在構(gòu)建的Component所屬的Component

$$typeof:一個(gè)我們不常見到的屬性壹堰,它被賦值為REACT_ELEMENT_TYPE

var REACT_ELEMENT_TYPE =
  (typeof Symbol === "function" && Symbol.for && Symbol.for("react.element")) ||
  0xeac7;

可見拭卿,$$typeof是一個(gè)Symbol類型的變量,這個(gè)變量可以防止XSS贱纠。

如果你的服務(wù)器有一個(gè)漏洞峻厚,允許用戶存儲任意JSON對象, 而客戶端代碼需要一個(gè)字符串谆焊,這可能會成為一個(gè)問題:

// JSON
let expectedTextButGotJSON = {
  type: "div",
  props: {
    dangerouslySetInnerHTML: {
      __html: "/* put your exploit here */"
    }
  }
};
let message = { text: expectedTextButGotJSON };
<p>{message.text}</p>;

JSON中不能存儲Symbol類型的變量惠桃。

ReactElement.isValidElement函數(shù)用來判斷一個(gè)React組件是否是有效的,下面是它的具體實(shí)現(xiàn)辖试。

ReactElement.isValidElement = function(object) {
  return (
    typeof object === "object" &&
    object !== null &&
    object.$$typeof === REACT_ELEMENT_TYPE
  );
};

可見React渲染時(shí)會把沒有$$typeof標(biāo)識辜王,以及規(guī)則校驗(yàn)不通過的組件過濾掉。

當(dāng)你的環(huán)境不支持Symbol時(shí)罐孝,$$typeof被賦值為0xeac7呐馆,至于為什么,React開發(fā)者給出了答案:

0xeac7看起來有點(diǎn)像React莲兢。

self汹来、source只有在非生產(chǎn)環(huán)境才會被加入對象中续膳。

  • self指定當(dāng)前位于哪個(gè)組件實(shí)例。
  • _source指定調(diào)試代碼來自的文件(fileName)和代碼行數(shù)(lineNumber)收班。

虛擬 DOM 轉(zhuǎn)換為真實(shí) DOM

上面我們分析了代碼轉(zhuǎn)換成了虛擬DOM的過程坟岔,下面來看一下React如何將虛擬DOM轉(zhuǎn)換成真實(shí)DOM

本部分邏輯較復(fù)雜摔桦,我們先用流程圖梳理一下整個(gè)過程社付,整個(gè)過程大概可分為四步:

過程 1:初始參數(shù)處理

在編寫好我們的React組件后,我們需要調(diào)用ReactDOM.render(element, container[, callback])將組件進(jìn)行渲染邻耕。

render函數(shù)內(nèi)部實(shí)際調(diào)用了_renderSubtreeIntoContainer瘦穆,我們來看看它的具體實(shí)現(xiàn):

  render: function (nextElement, container, callback) {
    return ReactMount._renderSubtreeIntoContainer(null, nextElement, container, callback);
  },
  • 1.將當(dāng)前組件使用TopLevelWrapper進(jìn)行包裹

TopLevelWrapper只一個(gè)空殼,它為你需要掛載的組件提供了一個(gè)rootID屬性赊豌,并在render函數(shù)中返回該組件。

TopLevelWrapper.prototype.render = function() {
  return this.props.child;
};

ReactDOM.render函數(shù)的第一個(gè)參數(shù)可以是原生DOM也可以是React組件绵咱,包裹一層TopLevelWrapper可以在后面的渲染中將它們進(jìn)行統(tǒng)一處理碘饼,而不用關(guān)心是否原生。

  • 2.判斷根結(jié)點(diǎn)下是否已經(jīng)渲染過元素悲伶,如果已經(jīng)渲染過艾恼,判斷執(zhí)行更新或者卸載操作
  • 3.處理shouldReuseMarkup變量,該變量表示是否需要重新標(biāo)記元素
  • 4.調(diào)用將上面處理好的參數(shù)傳入_renderNewRootComponent麸锉,渲染完成后調(diào)用callback钠绍。

_renderNewRootComponent中調(diào)用instantiateReactComponent對我們傳入的組件進(jìn)行分類包裝:

根據(jù)組件的類型,React根據(jù)原組件創(chuàng)建了下面四大類組件花沉,對組件進(jìn)行分類渲染:

  • ReactDOMEmptyComponent:空組件
  • ReactDOMTextComponent:文本
  • ReactDOMComponent:原生DOM
  • ReactCompositeComponent:自定義React組件

他們都具備以下三個(gè)方法:

  • construct:用來接收ReactElement進(jìn)行初始化柳爽。
  • mountComponent:用來生成ReactElement對應(yīng)的真實(shí)DOMDOMLazyTree
  • unmountComponent:卸載DOM節(jié)點(diǎn)碱屁,解綁事件磷脯。

具體是如何渲染我們在過程 3 中進(jìn)行分析。

過程 2:批處理娩脾、事務(wù)調(diào)用

_renderNewRootComponent中使用ReactUpdates.batchedUpdates調(diào)用batchedMountComponentIntoNode進(jìn)行批處理赵誓。

ReactUpdates.batchedUpdates(
  batchedMountComponentIntoNode,
  componentInstance,
  container,
  shouldReuseMarkup,
  context
);

batchedMountComponentIntoNode中,使用transaction.perform調(diào)用mountComponentIntoNode讓其基于事務(wù)機(jī)制進(jìn)行調(diào)用柿赊。

transaction.perform(
  mountComponentIntoNode,
  null,
  componentInstance,
  container,
  transaction,
  shouldReuseMarkup,
  context
);

關(guān)于批處理事務(wù)俩功,在我前面的分析setState 執(zhí)行機(jī)制中有更多介紹。

過程 3:生成 html

mountComponentIntoNode函數(shù)中調(diào)用ReactReconciler.mountComponent生成原生DOM節(jié)點(diǎn)碰声。

mountComponent內(nèi)部實(shí)際上是調(diào)用了過程 1 生成的四種對象的mountComponent方法诡蜓。首先來看一下ReactDOMComponent

  • 1.對特殊DOM標(biāo)簽、props進(jìn)行處理奥邮。
  • 2.根據(jù)標(biāo)簽類型創(chuàng)建DOM節(jié)點(diǎn)万牺。
  • 3.調(diào)用_updateDOMPropertiesprops插入到DOM節(jié)點(diǎn)罗珍,_updateDOMProperties也可用于props Diff,第一個(gè)參數(shù)為上次渲染的props脚粟,第二個(gè)參數(shù)為當(dāng)前props覆旱,若第一個(gè)參數(shù)為空,則為首次創(chuàng)建核无。
  • 4.生成一個(gè)DOMLazyTree對象并調(diào)用_createInitialChildren將孩子節(jié)點(diǎn)渲染到上面扣唱。

那么為什么不直接生成一個(gè)DOM節(jié)點(diǎn)而是要?jiǎng)?chuàng)建一個(gè)DOMLazyTree呢?我們先來看看_createInitialChildren做了什么:

判斷當(dāng)前節(jié)點(diǎn)的dangerouslySetInnerHTML屬性团南、孩子節(jié)點(diǎn)是否為文本和其他節(jié)點(diǎn)分別調(diào)用DOMLazyTreequeueHTML噪沙、queueTextqueueChild吐根。

可以發(fā)現(xiàn):DOMLazyTree實(shí)際上是一個(gè)包裹對象正歼,node屬性中存儲了真實(shí)的DOM節(jié)點(diǎn),children拷橘、html局义、text分別存儲孩子、html 節(jié)點(diǎn)和文本節(jié)點(diǎn)冗疮。

它提供了幾個(gè)方法用于插入孩子萄唇、html以及文本節(jié)點(diǎn),這些插入都是有條件限制的术幔,當(dāng)enableLazy屬性為true時(shí)另萤,這些孩子、html以及文本節(jié)點(diǎn)會被插入到DOMLazyTree對象中诅挑,當(dāng)其為false時(shí)會插入到真實(shí)DOM節(jié)點(diǎn)中四敞。

var enableLazy =
  (typeof document !== "undefined" &&
    typeof document.documentMode === "number") ||
  (typeof navigator !== "undefined" &&
    typeof navigator.userAgent === "string" &&
    /\bEdge\/\d/.test(navigator.userAgent));

可見:enableLazy是一個(gè)變量,當(dāng)前瀏覽器是IEEdge時(shí)為true拔妥。

IE(8-11)Edge瀏覽器中目养,一個(gè)一個(gè)插入無子孫的節(jié)點(diǎn),效率要遠(yuǎn)高于插入一整個(gè)序列化完整的節(jié)點(diǎn)樹毒嫡。

所以lazyTree主要解決的是在IE(8-11)Edge瀏覽器中插入節(jié)點(diǎn)的效率問題癌蚁,在后面的過程 4 我們會分析到:若當(dāng)前是IEEdge,則需要遞歸插入DOMLazyTree中緩存的子節(jié)點(diǎn)兜畸,其他瀏覽器只需要插入一次當(dāng)前節(jié)點(diǎn)努释,因?yàn)樗麄兊暮⒆右呀?jīng)被渲染好了,而不用擔(dān)心效率問題咬摇。

下面來看一下ReactCompositeComponent伐蒂,由于代碼非常多這里就不再貼這個(gè)模塊的代碼,其內(nèi)部主要做了以下幾步:

  • 處理props肛鹏、contex等變量逸邦,調(diào)用構(gòu)造函數(shù)創(chuàng)建組件實(shí)例
  • 判斷是否為無狀態(tài)組件恩沛,處理state
  • 調(diào)用performInitialMount生命周期,處理子節(jié)點(diǎn)缕减,獲取markup雷客。
  • 調(diào)用componentDidMount生命周期

performInitialMount函數(shù)中,首先調(diào)用了componentWillMount生命周期桥狡,由于自定義的React組件并不是一個(gè)真實(shí)的 DOM搅裙,所以在函數(shù)中又調(diào)用了孩子節(jié)點(diǎn)的mountComponent。這也是一個(gè)遞歸的過程裹芝,當(dāng)所有孩子節(jié)點(diǎn)渲染完成后部逮,返回markup并調(diào)用componentDidMount

過程 4:渲染 html

mountComponentIntoNode函數(shù)中調(diào)用將上一步生成的markup插入container容器嫂易。

在首次渲染時(shí)兄朋,_mountImageIntoNode會清空container的子節(jié)點(diǎn)后調(diào)用DOMLazyTree.insertTreeBefore

判斷是否為fragment節(jié)點(diǎn)或者<object>插件:

  • 如果是以上兩種,首先調(diào)用insertTreeChildren將此節(jié)點(diǎn)的孩子節(jié)點(diǎn)渲染到當(dāng)前節(jié)點(diǎn)上怜械,再將渲染完的節(jié)點(diǎn)插入到html

  • 如果是其他節(jié)點(diǎn)蜈漓,先將節(jié)點(diǎn)插入到插入到html,再調(diào)用insertTreeChildren將孩子節(jié)點(diǎn)插入到html宫盔。

  • 若當(dāng)前不是IEEdge,則不需要再遞歸插入子節(jié)點(diǎn)享完,只需要插入一次當(dāng)前節(jié)點(diǎn)灼芭。

  • 判斷不是IEbEdge時(shí)return
  • children不為空,遞歸insertTreeBefore進(jìn)行插入
  • 渲染 html 節(jié)點(diǎn)
  • 渲染文本節(jié)點(diǎn)

原生 DOM 事件代理

有關(guān)虛擬DOM的事件機(jī)制般又,我曾專門寫過一篇文章彼绷,有興趣可以 ??【React 深入】React 事件機(jī)制

虛擬 DOM 原理、特性總結(jié)

React 組件的渲染流程

  • 使用React.createElementJSX編寫React組件茴迁,實(shí)際上所有的JSX代碼最后都會轉(zhuǎn)換成React.createElement(...)寄悯,Babel幫助我們完成了這個(gè)轉(zhuǎn)換的過程。

  • createElement函數(shù)對keyref等特殊的props進(jìn)行處理堕义,并獲取defaultProps對默認(rèn)props進(jìn)行賦值猜旬,并且對傳入的孩子節(jié)點(diǎn)進(jìn)行處理,最終構(gòu)造成一個(gè)ReactElement對象(所謂的虛擬DOM)倦卖。

  • ReactDOM.render將生成好的虛擬DOM渲染到指定容器上洒擦,其中采用了批處理、事務(wù)等機(jī)制并且對特定瀏覽器進(jìn)行了性能優(yōu)化怕膛,最終轉(zhuǎn)換為真實(shí)DOM熟嫩。

虛擬 DOM 的組成

ReactElementelement 對象,我們的組件最終會被渲染成下面的結(jié)構(gòu):

  • type:元素的類型褐捻,可以是原生 html 類型(字符串)掸茅,或者自定義組件(函數(shù)或class
  • key:組件的唯一標(biāo)識椅邓,用于Diff算法,下面會詳細(xì)介紹
  • ref:用于訪問原生dom節(jié)點(diǎn)
  • props:傳入組件的props昧狮,chidrenprops中的一個(gè)屬性景馁,它存儲了當(dāng)前組件的孩子節(jié)點(diǎn),可以是數(shù)組(多個(gè)孩子節(jié)點(diǎn))或?qū)ο螅ㄖ挥幸粋€(gè)孩子節(jié)點(diǎn))
  • owner:當(dāng)前正在構(gòu)建的Component所屬的Component
  • self:(非生產(chǎn)環(huán)境)指定當(dāng)前位于哪個(gè)組件實(shí)例
  • _source:(非生產(chǎn)環(huán)境)指定調(diào)試代碼來自的文件(fileName)和代碼行數(shù)(lineNumber)

防止 XSS

ReactElement對象還有一個(gè)$$typeof屬性陵且,它是一個(gè)Symbol類型的變量Symbol.for('react.element')裁僧,當(dāng)環(huán)境不支持Symbol時(shí),$$typeof被賦值為0xeac7慕购。

這個(gè)變量可以防止XSS聊疲。如果你的服務(wù)器有一個(gè)漏洞,允許用戶存儲任意JSON對象沪悲, 而客戶端代碼需要一個(gè)字符串获洲,這可能為你的應(yīng)用程序帶來風(fēng)險(xiǎn)。JSON中不能存儲Symbol類型的變量殿如,而React渲染時(shí)會把沒有$$typeof標(biāo)識的組件過濾掉贡珊。

批處理和事務(wù)

React在渲染虛擬DOM時(shí)應(yīng)用了批處理以及事務(wù)機(jī)制,以提高渲染性能涉馁。

關(guān)于批處理以及事務(wù)機(jī)制门岔,在我之前的文章【React 深入】setState 的執(zhí)行機(jī)制中有詳細(xì)介紹。

針對性的性能優(yōu)化

IE(8-11)Edge瀏覽器中烤送,一個(gè)一個(gè)插入無子孫的節(jié)點(diǎn)寒随,效率要遠(yuǎn)高于插入一整個(gè)序列化完整的節(jié)點(diǎn)樹。

React通過lazyTree帮坚,在IE(8-11)Edge中進(jìn)行單個(gè)節(jié)點(diǎn)依次渲染節(jié)點(diǎn)妻往,而在其他瀏覽器中則首先將整個(gè)大的DOM結(jié)構(gòu)構(gòu)建好,然后再整體插入容器试和。

并且讯泣,在單獨(dú)渲染節(jié)點(diǎn)時(shí),React還考慮了fragment等特殊節(jié)點(diǎn)阅悍,這些節(jié)點(diǎn)則不會一個(gè)一個(gè)插入渲染好渠。

虛擬 DOM 事件機(jī)制

React自己實(shí)現(xiàn)了一套事件機(jī)制,其將所有綁定在虛擬DOM上的事件映射到真正的DOM事件节视,并將所有的事件都代理到document上晦墙,自己模擬了事件冒泡和捕獲的過程,并且進(jìn)行統(tǒng)一的事件分發(fā)肴茄。

React自己構(gòu)造了合成事件對象SyntheticEvent晌畅,這是一個(gè)跨瀏覽器原生事件包裝器。 它具有與瀏覽器原生事件相同的接口寡痰,包括stopPropagation()preventDefault()等等抗楔,在所有瀏覽器中他們工作方式都相同棋凳。這抹平了各個(gè)瀏覽器的事件兼容性問題。

上面只分析虛擬DOM首次渲染的原理和過程连躏,當(dāng)然這并不包括虛擬 DOM進(jìn)行 Diff的過程剩岳,下一篇文章我們再來詳細(xì)探討。

關(guān)于開篇提的幾個(gè)問題入热,我們在下篇文章中進(jìn)行統(tǒng)一回答拍棕。

末尾

文中如有錯(cuò)誤,歡迎在評論區(qū)指正勺良,或者您對文章的排版绰播,閱讀體驗(yàn)有什么好的建議,歡迎在評論區(qū)指出尚困,謝謝閱讀蠢箩。


歡迎大家到公眾號: you的日常 閱讀,體驗(yàn)更好哦事甜。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末谬泌,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子逻谦,更是在濱河造成了極大的恐慌掌实,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件邦马,死亡現(xiàn)場離奇詭異贱鼻,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)勇婴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來嘱腥,“玉大人耕渴,你說我怎么就攤上這事〕萃茫” “怎么了橱脸?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長分苇。 經(jīng)常有香客問我添诉,道長,這世上最難降的妖魔是什么医寿? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任栏赴,我火速辦了婚禮,結(jié)果婚禮上靖秩,老公的妹妹穿的比我還像新娘须眷。我一直安慰自己竖瘾,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布花颗。 她就那樣靜靜地躺著捕传,像睡著了一般。 火紅的嫁衣襯著肌膚如雪扩劝。 梳的紋絲不亂的頭發(fā)上庸论,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天,我揣著相機(jī)與錄音棒呛,去河邊找鬼聂示。 笑死,一個(gè)胖子當(dāng)著我的面吹牛条霜,可吹牛的內(nèi)容都是我干的催什。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼宰睡,長吁一口氣:“原來是場噩夢啊……” “哼蒲凶!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起拆内,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤旋圆,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后麸恍,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體灵巧,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年抹沪,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了刻肄。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,690評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡融欧,死狀恐怖敏弃,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情噪馏,我是刑警寧澤麦到,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站欠肾,受9級特大地震影響瓶颠,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜刺桃,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一粹淋、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦廓啊、人聲如沸欢搜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽炒瘟。三九已至,卻和暖如春第步,著一層夾襖步出監(jiān)牢的瞬間疮装,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工粘都, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留廓推,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓翩隧,卻偏偏與公主長得像樊展,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子堆生,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評論 2 353

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

  • 以下內(nèi)容是我在學(xué)習(xí)和研究React時(shí)专缠,對React的特性、重點(diǎn)和注意事項(xiàng)的提取淑仆、精練和總結(jié)涝婉,可以做為React特性...
    科研者閱讀 8,232評論 2 21
  • 40、React 什么是React蔗怠?React 是一個(gè)用于構(gòu)建用戶界面的框架(采用的是MVC模式):集中處理VIE...
    萌妹撒閱讀 1,014評論 0 1
  • 3. JSX JSX是對JavaScript語言的一個(gè)擴(kuò)展語法墩弯, 用于生產(chǎn)React“元素”,建議在描述UI的時(shí)候...
    pixels閱讀 2,823評論 0 24
  • 深入JSX date:20170412筆記原文其實(shí)JSX是React.createElement(componen...
    gaoer1938閱讀 8,061評論 2 35
  • React基礎(chǔ) React組件化編程 Create React App 創(chuàng)建React 前端工程 題外話題:頁面性...
    BeautifulHao閱讀 1,542評論 0 3