當前位置:
首頁 > 最新 > 【第961期】圖解 React Virtual DOM

【第961期】圖解 React Virtual DOM

前言

又周末了,可以出去走走了。今日早讀文章由滬江網前端工程師@阿希分享。

正文從這開始~

了解 React 的人幾乎都聽過說 Virtual DOM,甚至不了解 React 的人也聽過 Virtual DOM。那麼 React 的 Virtual DOM 到底長什麼樣子呢?今天我們將一探 React 的源碼來揭開 React Virtual DOM 的神秘面紗。

參考源碼為React穩定版,版本號v15.4.1。

1. React

我們首先試著在控制台列印一下 React 看看會是什麼樣子:

從控制台看來,React是一個對象,那接下來我們找到相應的源碼來確認看看(src/isomorphic/React.js):

varReact={

Children:{

map:ReactChildren.map,

forEach:ReactChildren.forEach,

count:ReactChildren.count,

toArray:ReactChildren.toArray,

only:onlyChild,

},

Component:ReactComponent,

PureComponent:ReactPureComponent,

createElement:createElement,

cloneElement:cloneElement,

isValidElement:ReactElement.isValidElement,

PropTypes:ReactPropTypes,

createClass:ReactClass.createClass,

createFactory:createFactory,

createMixin:function(mixin){returnmixin;

},

DOM:ReactDOMFactories,

version:ReactVersion,

__spread:__spread,

};

可以了解到,React 確實是一個 Object ,我們可以把 React 對象畫成下圖的形式,方便大家直觀的觀察:

React 是一個對象,裡面包含了許多方法和屬性,有最新的 v15 版本的方法,也有些以前的 API 和一些已經廢棄不建議使用的 API。

Component 用來創建 React 組件類。

PureComponent 用來創建 React 純組件類。

createElement 創建 React 元素。

cloneElement 拷貝 React 元素。

isValidElement 判斷是否是有效的 React 元素。

PropTypes 定義 React props 類型。(過時的API)

createClass 創建 React 組件類(過時的API)。

createFactory 創建 React 工廠函數。(不建議使用)。

createMixin 創建 Mixin。

DOM 主要和同構相關。

version 當前使用的 React 版本號。

__spread 已廢棄,直接用 Object.assign() 代替

__spread 方法已經廢棄,不再建議使用。在作者寫這篇文章的時候,React 又發布了 v15.5.0 版本,在這個版本里,createClass 和 PropTypes 也已經被標記為過時的 API,會提示 warning。

對於原來的舊 API React.createClass,現在推薦開發者用 class 的方式繼承 Component 或者 PureComponent。

對於 PropTypes 的引入方式也不是原來的 import { PropTypes } from react ,而變成了 import PropTypes from prop-types 。

其他屬性和方法我們暫且就不詳細的講述了,這篇文章就只詳細的研究一下和創建 React Virtual DOM 最緊密相關的方法——React.createElement。

React.createElement 方法其實是調用的ReactElement模塊的 ReactElement.createElement 方法。

2. React Element

Virtual DOM 是真實 DOM 的模擬,真實 DOM 是由真實的 DOM 元素構成,Virtual DOM 也是由虛擬的 DOM 元素構成。真實 DOM 元素我們已經很熟悉了,它們都是 HTML 元素(HTML Element)。那虛擬 DOM 元素是什麼呢?React 給虛擬 DOM 元素取名叫 React 元素(React Element)。

我們知道,React 可以通過組合一些 HTML 原生元素形成組件,然後組件又可以被其他的組件復用。所以,原生元素和組件其實在概念上都是一致的,都是具有特定功能和 UI 的可復用的元素。因此,React 把這些元素抽象成了 React Element。不論是 HTML 原生元素,例如:

,,等。或者這些原生元素的組合(組件),例如 等。它們都是 React Element,而創建這些 Element 的方法就是 React.createElement。

React Virtual DOM 就是由 React Element 構成的一棵樹。

接下來我們就探究下 React Element 到底長什麼樣以及 React 是如何創建這些 React Element 的。

2.1 ReactElement 模塊

我們在控制台里直接列印出 hello:

我們再列印出 ,App 組件的結構如下:

App

Hello world!

列印出的結果如下:

可以很直觀的發現,列印的 HTML 元素並不是真實的 DOM 元素,列印的組件也不是 DOM 元素的集合,所有列印出來的元素都是一個對象,而且它們長的非常相似,那其實這些對象都是 React Element 對象。

然後我們再看看源碼部分:

varReactElement=function(type,key,ref,self,source,owner,props){varelement={

$$typeof:REACT_ELEMENT_TYPE,

type:type,

key:key,

ref:ref,

props:props,

_owner:owner,

};if(__DEV__){// ...

}returnelement;

};

ReactElement其實是一個工廠函數,接受7個參數,最終返回一個React Element對象。

$$type React Element 的標誌,是一個Symbol類型。

type React 元素的類型。

key React 元素的 key,diff 演算法會用到。

ref React 元素的 ref 屬性,當 React 元素生成實際 DOM 後,返回 DOM 的引用。

props React 元素的屬性,是一個對象。

_owner 負責創建這個 React 元素的組件。

參數中的 self 和 source 都是只供開發環境下用的參數。從上面的例子我們可以發現唯一不同的就是type 了,對於原生元素,type 是一個字元串類型,記錄了原生元素的類型;對於 react 組件來說呢,type 是一個構造函數,或者說它是一個類,記錄了這個 react 組件的是哪一個類的實例。所以.type === App 的。

所以,每一個包裝過後的React元素都是這樣的對象:

{

$$typeof:REACT_ELEMENT_TYPE,

type:type,

key:key,

ref:ref,

props:props,

_owner:owner,

}

用圖片表示 React Element,就是下圖這樣:

2.2 ReactElement.createElement 方法

在此之前,可能有人會問,我們開發當中似乎沒有用到 React.createElement 方法呀。其實不然,看下面的示例:

classOriginalElementextendsComponent{

render(){

return(

Original Element div

);

}

}

經過babel轉譯之後是這樣的

_createClass(OriginalElement,[{

key:"render",

value:functionrender(){

returnReact.createElement(

"div",

null,

"Original Element div"

);

}

}]);

可以看到,所有的 JSX 都會被編譯成 React.createElement 方法,所以這個方法可能是我們在使用React用的最多的方法。

接下來我們看看 React.createElement 方法是怎樣的,前面說過了 React.createElement 方法其實就是 ReactElement.createElement 方法。

ReactElement.createElement=function(type,config,children){

varpropName;

varprops={};

varkey=null;

varref=null;

varself=null;

varsource=null;

if(config!=null){

if(hasValidRef(config)){

ref=config.ref;

}

if(hasValidKey(config)){

key= +config.key;

}

self=config.__self===undefined?null:config.__self;

source=config.__source===undefined?null:config.__source;

for(propNameinconfig){

if(hasOwnProperty.call(config,propName)&&

!RESERVED_PROPS.hasOwnProperty(propName)){

props[propName]=config[propName];

}

}

}

varchildrenLength=arguments.length-2;

if(childrenLength===1){

props.children=children;

}elseif(childrenLength>1){

varchildArray=Array(childrenLength);

for(vari=;i

childArray[i]=arguments[i+2];

}

if(__DEV__){

// ...

}

props.children=childArray;

}

if(type&&type.defaultProps){

vardefaultProps=type.defaultProps;

for(propNameindefaultProps){

if(props[propName]===undefined){

props[propName]=defaultProps[propName];

}

}

}

if(__DEV__){

// ...

}

returnReactElement(

type,

key,

ref,

self,

source,

ReactCurrentOwner.current,

props

);

};

reactElement.createElement大致做了2件事。

第一件是初始化 React Element 里的各種參數,例如 type,props 和 children 等。在初始化的時候,會提取出 key,ref 這兩個屬性,然後 __self,__source 這兩個屬性也是僅開發用。所以如果你在組件里定義了 key,ref,__self,__source 這4個屬性中的任何一個,都是不能在 this.props 里訪問到的。從第三個參數開始,傳入的參數都會合并為 children 屬性,如果只有一個,那麼 children 就是第三個元素,如果超過一個,那麼這些元素就會合并成一個 children 數組。

第二件是初始化 defaultProps,我們可以發現,defaultProps 是通過 type 來初始化的,我們在上面也說過,對於 react 組件來說,type 是 React Element 所屬的類,所以可以通過 type 取到該類的 defaultProps(默認屬性)。這裡還有一點需要注意,如果我們把某個屬性的值定義成 undefined,那麼這個屬性也會使用默認屬性,但是定義成 null 就不會使用默認屬性。

下面是圖解:

4. 創建Virtual DOM樹

有了上面的作為基礎,那創建 Virtual DOM 就很簡單了。整個 Virtual DOM 就是一個巨大的對象。

比如我們有這麼一個 App:

App:

Header:

菜單

List:

text1

text2

text3

Logo:

text logo

ReactDOM.render(,document.getElementById( root ))

通過上面的了解到的 React Element 創建方式,我們不難知道,生成的對應的 Virtual DOM 應該是類似於這樣的:

需要注意的是,這些元素並不是真實的 DOM 元素, 它們只是一些對象,而且我們可以看到 React 組件實際上是概念上的形態,最終還是會生成原生的虛擬 DOM 對象。當這些對象上的數據發生變化時,通過打 patch 把變化同步到真實的 DOM 上去。

目前我們可以認為 Virtual DOM 就是這樣的一種形態,但是實際上,並沒有這麼簡單,這只是最基本的樣子,在後續的文章中我會帶大家一起看看更高級的形態。

關於本文

作者:滬江網@阿希

原文:http://dwz.cn/65s77c


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 前端早讀課 的精彩文章:

ES6 modules 即將到來,現在該考慮新的打包方案了嘛?
第三方Javascript開發系列之前後端介面協議
理解Node事件驅動架構
【第957期】JavaScript 模塊現狀
「零廣告,全乾貨」iWeb峰會上海站,最後500位參會名額限免來襲!

TAG:前端早讀課 |

您可能感興趣

Moto One Vision正式亮相:25MP+Exynos 9610,價格感人
摩托羅拉新機One Vision曝光:打孔屏+Exynos 9610處理器
寶姿PORTS 1961 推出全新LACE42 Sneaker
三星Galaxy A50曝光:Exynos 9610+後置三攝
三星Galaxy A50s跑分曝光,搭載Exynos 9610
抖森新劇轉票,全新Ports 1961襯衫,Sprain珍珠項鏈
和Ports 1961在拍賣行玩一場時裝遊戲,看Simone Rocha在黑暗中綻放的花朵
三星推出隸屬於Exynos 7系列的Exynos 9610處理器
模型製作範例:未能留世的經典 Ferrari Dino 156F1 2 1961
Ports 1961 2018春夏男裝廣告大片發布:男裝創意總監Milan V親自掌鏡!
三星Galaxy A50更多配置曝光:Exynos 9610處理器+4000mAh電池
集成AI/480fps慢攝 三星Exynos 9610發布
油畫-Cuno Amiet 1868-1961
三星推Exynos 9610處理器:IPS加強 支持48FPS慢動作
三星親兒子魅藍Note8泄露:或全球首發Exynos9610
三星8核10nm新U Exynos 9610發布,主打AI,你猜哪家廠商最開心?
三星發布Exynos 9610:10nm工藝
專攻中端手機市場,10nm製程,三星 Exynos 9610正式發布!
三星A50s亮相:Exynos9610+25MP,價格感人!
三星推出Exynos 9610:10nm工藝八核心設計