導(dǎo)讀
React
的虛擬DOM
和Diff
算法是React
的非常重要的核心特性容为,這部分源碼也非常復(fù)雜,理解這部分知識的原理對更深入的掌握React
是非常必要的隔箍。
本來想將虛擬DOM
和Diff
算法放到一篇文章谓娃,寫完虛擬DOM
發(fā)現(xiàn)文章已經(jīng)很長了,所以本篇只分析虛擬DOM
蜒滩。
本篇文章從源碼出發(fā)滨达,分析虛擬DOM
的核心渲染原理(首次渲染),以及React
對它做的性能優(yōu)化點(diǎn)俯艰。
說實(shí)話React
源碼真的很難讀 ??捡遍,如果本篇文章幫助到了你,那么請給個(gè)贊 ?? 支持一下吧竹握。
開發(fā)中的常見問題
- 為何必須引用
React
- 自定義的
React
組件為何必須大寫 -
React
如何防止XSS
-
React
的Diff
算法和其他的Diff
算法有何區(qū)別 -
key
在React
中的作用 - 如何寫出高性能的
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)勢在于React
的Diff
算法和批處理策略疆液,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è)瀏覽器的事件兼容性問題涵卵。
跨平臺兼容
VitrualDom
為React
帶來了跨平臺渲染的能力浴栽。以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
凛辣、key
從config
中取出并賦值 - 2.將特殊屬性
self
、source
從config
中取出并賦值 - 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í)DOM
或DOMLazyTree
。 -
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)用
_updateDOMProperties
將props
插入到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)用DOMLazyTree
的queueHTML
噪沙、queueText
、queueChild
吐根。
可以發(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)前瀏覽器是IE
或Edge
時(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)前是IE
或Edge
,則需要遞歸插入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)前不是
IE
或Edge
,則不需要再遞歸插入子節(jié)點(diǎn)享完,只需要插入一次當(dāng)前節(jié)點(diǎn)灼芭。
- 判斷不是
IE
或bEdge
時(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.createElement
或JSX
編寫React
組件茴迁,實(shí)際上所有的JSX
代碼最后都會轉(zhuǎn)換成React.createElement(...)
寄悯,Babel
幫助我們完成了這個(gè)轉(zhuǎn)換的過程。createElement
函數(shù)對key
和ref
等特殊的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 的組成
即ReactElement
element 對象,我們的組件最終會被渲染成下面的結(jié)構(gòu):
-
type
:元素的類型褐捻,可以是原生 html 類型(字符串)掸茅,或者自定義組件(函數(shù)或class
) -
key
:組件的唯一標(biāo)識椅邓,用于Diff
算法,下面會詳細(xì)介紹 -
ref
:用于訪問原生dom
節(jié)點(diǎn) -
props
:傳入組件的props
昧狮,chidren
是props
中的一個(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)更好哦事甜。