做者: 阿希 (滬江Web前端開發工程師)
本文原創,轉載請註明做者及出處。html
瞭解 React 的人幾乎都聽過說 Virtual DOM,甚至不瞭解 React 的人也聽過 Virtual DOM。那麼 React 的 Virtual DOM 到底長什麼樣子呢?今天咱們將一探 React 的源碼來揭開 React Virtual DOM 的神祕面紗。前端
參考源碼爲React穩定版,版本號v15.4.1。react
咱們首先試着在控制檯打印一下 React
看看會是什麼樣子:算法
從控制檯看來,React是一個對象,那接下來咱們找到相應的源碼來確認看看(src/isomorphic/React.js):數組
var React = { 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) { return mixin; }, DOM: ReactDOMFactories, version: ReactVersion, __spread: __spread, };
能夠了解到,React 確實是一個 Object ,咱們能夠把 React 對象畫成下圖的形式,方便你們直觀的觀察:babel
React 是一個對象,裏面包含了許多方法和屬性,有最新的 v15 版本的方法,也有些之前的 API 和一些已經廢棄不建議使用的 API。函數
Component
用來建立 React 組件類。this
PureComponent
用來建立 React 純組件類。spa
createElement
建立 React 元素。code
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
方法。
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 原生元素,例如:<p></p>
,<a></a>
,等。或者這些原生元素的組合(組件),例如 <Message />
等。它們都是 React Element,而建立這些 Element 的方法就是 React.createElement
。
React Virtual DOM 就是由 React Element 構成的一棵樹。
接下來咱們就探究下 React Element 到底長什麼樣以及 React 是如何建立這些 React Element 的。
咱們在控制檯裏直接打印出 <h1>hello</h1>
:
咱們再打印出 <App />
,App 組件的結構以下:
<div> <h1>App</h1> <p>Hello world!</p> </div>
打印出的結果以下:
能夠很直觀的發現,打印的 HTML 元素並非真實的 DOM 元素,打印的組件也不是 DOM 元素的集合,全部打印出來的元素都是一個對象,並且它們長的很是類似,那其實這些對象都是 React Element 對象。
而後咱們再看看源碼部分:
var ReactElement = function(type, key, ref, self, source, owner, props) { var element = { $$typeof: REACT_ELEMENT_TYPE, type: type, key: key, ref: ref, props: props, _owner: owner, }; if (__DEV__) { // ... } return element; };
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 組件的是哪個類的實例。因此<App/>.type === App
的。
因此,每個包裝事後的React元素都是這樣的對象:
{ $$typeof: REACT_ELEMENT_TYPE, type: type, key: key, ref: ref, props: props, _owner: owner, }
用圖片表示 React Element,就是下圖這樣:
在此以前,可能有人會問,咱們開發當中彷佛沒有用到 React.createElement 方法呀。其實否則,看下面的示例:
class OriginalElement extends Component { render() { return ( <div>Original Element div</div> ); } }
通過babel轉譯以後是這樣的
_createClass(OriginalElement, [{ key: "render", value: function render() { return React.createElement( "div", null, "Original Element div" ); } }]);
能夠看到,全部的 JSX 都會被編譯成 React.createElement 方法,因此這個方法多是咱們在使用React用的最多的方法。
接下來咱們看看 React.createElement 方法是怎樣的,前面說過了 React.createElement 方法其實就是 ReactElement.createElement 方法。
ReactElement.createElement = function(type, config, children) { var propName; var props = {}; var key = null; var ref = null; var self = null; var source = 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 (propName in config) { if (hasOwnProperty.call(config, propName) && !RESERVED_PROPS.hasOwnProperty(propName)) { props[propName] = config[propName]; } } } var childrenLength = arguments.length - 2; if (childrenLength === 1) { props.children = children; } else if (childrenLength > 1) { var childArray = Array(childrenLength); for (var i = 0; i < childrenLength; i++) { childArray[i] = arguments[i + 2]; } if (__DEV__) { // ... } props.children = childArray; } if (type && type.defaultProps) { var defaultProps = type.defaultProps; for (propName in defaultProps) { if (props[propName] === undefined) { props[propName] = defaultProps[propName]; } } } if (__DEV__) { // ... } return ReactElement( 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
就不會使用默認屬性。
下面是圖解:
有了上面的做爲基礎,那建立 Virtual DOM 就很簡單了。整個 Virtual DOM 就是一個巨大的對象。
好比咱們有這麼一個 App
:
App: <div> <Header /> <List /> </div> Header: <div> <Logo /> <button>菜單</button> </div> List: <ul> <li>text 1</li> <li>text 2</li> <li>text 3</li> </ul> Logo: <div> <img src="./foo.png" alt="logo" /> <p>text logo</p> </div> ReactDOM.render(<App />, document.getElementById('root'))
經過上面的瞭解到的 React Element 建立方式,咱們不難知道,生成的對應的 Virtual DOM 應該是相似於這樣的:
須要注意的是,這些元素並非真實的 DOM 元素, 它們只是一些對象,並且咱們能夠看到 React 組件其實是概念上的形態,最終仍是會生成原生的虛擬 DOM 對象。當這些對象上的數據發生變化時,經過打 patch 把變化同步到真實的 DOM 上去。
目前咱們能夠認爲 Virtual DOM 就是這樣的一種形態,可是實際上,並無這麼簡單,這只是最基本的樣子,在後續的文章中我會帶你們一塊兒看看更高級的形態。
iKcamp原創新書《移動Web前端高效開發實戰》已在亞馬遜、京東、噹噹開售。