React源碼筆記-虛擬dom

React源碼筆記鏈接

React源碼筆記-虛擬dom

React源碼筆記-mount過程

最近因為找實習看了一下React源碼(0.14版本),對於React中的虛擬dom、如何掛載和diff演算法(setState調用後的同步過程)有了一定的了解,所以準備寫三篇文章總結一下,這是第一篇講解react中的虛擬dom,希望各位讀者在發現有什麼不對時候進行指正。

虛擬dom

虛擬dom可以算是react中的最基礎的東西,react的掛載和同步都是基於虛擬dom來實現的。通過對閱讀源碼可以發現react中的虛擬dom主要分為3類:

  1. string類型的字元串或者number類型的數字,對應dom中的text node。
  2. 通過React.createElement創造出來的ReactElement對象,type為string類型,對應html標籤(比如div)。
  3. 通過React.createElement創造出來的ReactElement對象,type為function類型,一般是繼承React.Component的構造函數。

ReactElement

第一種類型的虛擬dom沒什麼好講的,其他兩種類型的虛擬dom都是ReactElement對象,一般通過React.createElement可以得到,React.createElement方法其實就是ReactElement上的靜態方法createElement,下面是ReactElement.createElement的源碼(在ReactElement.js文件中):

ReactElement.createElement = function(type, config, children) {n var propName;nn // Reserved names are extractedn var props = {};nn var key = null;n var ref = null;n var self = null;n var source = null;nn if (config != null) {n ref = config.ref === undefined ? null : config.ref;n key = config.key === undefined ? null : + config.key;n self = config.__self === undefined ? null : config.__self;n source = config.__source === undefined ? null : config.__source;n // Remaining properties are added to a new props objectn for (propName in config) {n if (config.hasOwnProperty(propName) &&n !RESERVED_PROPS.hasOwnProperty(propName)) {n props[propName] = config[propName];n }n }n }nn // Children can be more than one argument, and those are transferred onton // the newly allocated props object.n var childrenLength = arguments.length - 2;n if (childrenLength === 1) {n props.children = children;n } else if (childrenLength > 1) {n var childArray = Array(childrenLength);n for (var i = 0; i < childrenLength; i++) {n childArray[i] = arguments[i + 2];n }n props.children = childArray;n }nn // Resolve default propsn if (type && type.defaultProps) {n var defaultProps = type.defaultProps;n for (propName in defaultProps) {n if (typeof props[propName] === undefined) {n props[propName] = defaultProps[propName];n }n }n }nn return ReactElement(n type,n key,n ref,n self,n source,n ReactCurrentOwner.current,n propsn );n};n

代碼比較簡單,首先對參數進行處理,然後創建一個ReactElement對象,其中值得注意的是傳過來的children被放在了props中,下面我們來看看ReactElement方法的代碼(刪除了一些開發過程中需要的代碼):

var ReactElement = function(type, key, ref, self, source, owner, props) {n var element = {n // This tag allow us to uniquely identify this as a React Elementn $$typeof: REACT_ELEMENT_TYPE,nn // Built-in properties that belong on the elementn type: type,n key: key,n ref: ref,n props: props,nn // Record the component responsible for creating this element.n _owner: owner,n };nn return element;n};n

創建的對象包括常見的key、ref和props屬性。

ReactDOMTextComponent、ReactDOMComponent和ReactCompositeComponent

為了方便3種虛擬dom的掛載、卸載和更新,react在內部分別創建了以下三個類:

  1. ReactDOMTextComponent用來負責text node對應的虛擬dom。
  2. ReactDOMComponent用來負責html標籤對應的虛擬dom。
  3. ReactCompositeComponent用來負責繼承React.Component對應的虛擬dom。

對於這3個類,主要包含四個重要的方法:

  1. construct用來接收虛擬dom進行初始化。
  2. mountComponent接收一個標誌該元素的id(對應元素上的data-reactid屬性),並生成虛擬dom對應的node節點或者markup(用來掛載到dom樹上),其中因為text node和node節點在性質非常多的不同,為了一致的處理text node和node節點,react內部一般會用span標籤對text node進行包裹。

  3. unmountComponent是用來卸載內存中的虛擬dom(不會對dom樹進行處理,對於這一部分的說明將會在後面的文章中講解),並對事件進行解綁。

  4. receiveComponent接收虛擬dom的下一個狀態,一般會調用updateComponent方法來進行diff演算法(對於text node直接是替換內容,不調用updateComponent)。

對於ReactCompositeComponent類裡面還包括了一個performUpdateIfNecessary方法,這個方法一般會在調用setState後調用,這個方法會調用updateComponent進行diff演算法。

instantiateReactComponent

為了方便ReactTextNodeComponent、ReactDOMComponent和ReactCompositeComponent實例的創建和初始化,react引入了instantiateReactComponent這個工廠方法,通過傳入不同的虛擬dom來創建對應ReactXXXComponent對象。下面是instantiateReactComponent的核心代碼:

function instantiateReactComponent(node) {n var instance;nn if (node === null || node === false) {n instance = new ReactEmptyComponent(instantiateReactComponent);n } else if (typeof node === object) {n var element = node;n n // Special case string valuesn if (typeof element.type === string) {n // 創建ReactDOMComponent對象n instance = ReactNativeComponent.createInternalComponent(element);n } else if (isInternalComponentType(element.type)) {n // 如果type是ReactXXXComponent的話,直接創建對象n instance = new element.type(element);n } else {n // 創建ReactCompositeComponent對象n instance = new ReactCompositeComponentWrapper();n }n } else if (typeof node === string || typeof node === number) {n // 創建ReactDOMTextComponentn instance = ReactNativeComponent.createInstanceForText(node);n } else {n invariant(n false,n Encountered invalid React node of type %s,n typeof noden );n }nn n instance.construct(node);nn instance._mountIndex = 0;n instance._mountImage = null;nn return instance;n}n

總結

比較粗糙的分析了一下react中的虛擬dom,對於看到這裡的讀者真的是非常的感謝,如果您發現文中有什麼問題,希望能夠指正。
推薦閱讀:

初學前端時我們為什麼總是糾結相同的問題
相比 React 全家桶,選擇 Vue2 有何優劣?
奇舞周刊第 237 期:如何為 ThinkJS 3 網站優化 TTFB 時間

TAG:前端框架 | 前端开发 | React |