摘要:判斷是否是有效的元素。主要和同構(gòu)相關(guān)。是真實的模擬,真實是由真實的元素構(gòu)成,也是由虛擬的元素構(gòu)成。當這些對象上的數(shù)據(jù)發(fā)生變化時,通過打把變化同步到真實的上去。原創(chuàng)新書移動前端高效開發(fā)實戰(zhàn)已在亞馬遜京東當當開售。
作者: 阿希 (滬江Web前端開發(fā)工程師)
本文原創(chuàng),轉(zhuǎn)載請注明作者及出處。
了解 React 的人幾乎都聽過說 Virtual DOM,甚至不了解 React 的人也聽過 Virtual DOM。那么 React 的 Virtual DOM 到底長什么樣子呢?今天我們將一探 React 的源碼來揭開 React Virtual DOM 的神秘面紗。
1. React參考源碼為React穩(wěn)定版,版本號v15.4.1。
我們首先試著在控制臺打印一下 React 看看會是什么樣子:
從控制臺看來,React是一個對象,那接下來我們找到相應(yīng)的源碼來確認看看(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 對象畫成下圖的形式,方便大家直觀的觀察:
React 是一個對象,里面包含了許多方法和屬性,有最新的 v15 版本的方法,也有些以前的 API 和一些已經(jīng)廢棄不建議使用的 API。
Component 用來創(chuàng)建 React 組件類。
PureComponent 用來創(chuàng)建 React 純組件類。
createElement 創(chuàng)建 React 元素。
cloneElement 拷貝 React 元素。
isValidElement 判斷是否是有效的 React 元素。
PropTypes 定義 React props 類型。(過時的API)
createClass 創(chuàng)建 React 組件類(過時的API)。
createFactory 創(chuàng)建 React 工廠函數(shù)。(不建議使用)。
createMixin 創(chuàng)建 Mixin。
DOM 主要和同構(gòu)相關(guān)。
version 當前使用的 React 版本號。
__spread 已廢棄,直接用 Object.assign() 代替
__spread 方法已經(jīng)廢棄,不再建議使用。在作者寫這篇文章的時候,React 又發(fā)布了 v15.5.0 版本,在這個版本里,createClass 和 PropTypes 也已經(jīng)被標記為過時的 API,會提示 warning。
對于原來的舊 API React.createClass,現(xiàn)在推薦開發(fā)者用 class 的方式繼承 Component 或者 PureComponent。
對于 PropTypes 的引入方式也不是原來的 import { PropTypes } from "react",而變成了 import PropTypes from "prop-types"。
其他屬性和方法我們暫且就不詳細的講述了,這篇文章就只詳細的研究一下和創(chuàng)建 React Virtual DOM 最緊密相關(guān)的方法——React.createElement。
2. React ElementReact.createElement 方法其實是調(diào)用的ReactElement模塊的 ReactElement.createElement 方法。
Virtual DOM 是真實 DOM 的模擬,真實 DOM 是由真實的 DOM 元素構(gòu)成,Virtual DOM 也是由虛擬的 DOM 元素構(gòu)成。真實 DOM 元素我們已經(jīng)很熟悉了,它們都是 HTML 元素(HTML Element)。那虛擬 DOM 元素是什么呢?React 給虛擬 DOM 元素取名叫 React 元素(React Element)。
我們知道,React 可以通過組合一些 HTML 原生元素形成組件,然后組件又可以被其他的組件復(fù)用。所以,原生元素和組件其實在概念上都是一致的,都是具有特定功能和 UI 的可復(fù)用的元素。因此,React 把這些元素抽象成了 React Element。不論是 HTML 原生元素,例如:
,,等?;蛘哌@些原生元素的組合(組件),例如React Virtual DOM 就是由 React Element 構(gòu)成的一棵樹。
接下來我們就探究下 React Element 到底長什么樣以及 React 是如何創(chuàng)建這些 React Element 的。
2.1 ReactElement 模塊我們在控制臺里直接打印出 hello
:
我們再打印出
App
Hello world!
打印出的結(jié)果如下:
可以很直觀的發(fā)現(xiàn),打印的 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其實是一個工廠函數(shù),接受7個參數(shù),最終返回一個React Element對象。
$$type React Element 的標志,是一個Symbol類型。
type React 元素的類型。
key React 元素的 key,diff 算法會用到。
ref React 元素的 ref 屬性,當 React 元素生成實際 DOM 后,返回 DOM 的引用。
props React 元素的屬性,是一個對象。
_owner 負責創(chuàng)建這個 React 元素的組件。
參數(shù)中的 self 和 source 都是只供開發(fā)環(huán)境下用的參數(shù)。從上面的例子我們可以發(fā)現(xiàn)唯一不同的就是type 了,對于原生元素,type 是一個字符串類型,記錄了原生元素的類型;對于 react 組件來說呢,type 是一個構(gòu)造函數(shù),或者說它是一個類,記錄了這個 react 組件的是哪一個類的實例。所以
所以,每一個包裝過后的React元素都是這樣的對象:
{ $$typeof: REACT_ELEMENT_TYPE, type: type, key: key, ref: ref, props: props, _owner: owner, }
用圖片表示 React Element,就是下圖這樣:
2.2 ReactElement.createElement 方法在此之前,可能有人會問,我們開發(fā)當中似乎沒有用到 React.createElement 方法呀。其實不然,看下面的示例:
class OriginalElement extends Component { render() { return (Original Element div); } }
經(jīng)過babel轉(zhuǎn)譯之后是這樣的
_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 里的各種參數(shù),例如 type,props 和 children 等。在初始化的時候,會提取出 key,ref 這兩個屬性,然后 __self,__source 這兩個屬性也是僅開發(fā)用。所以如果你在組件里定義了 key,ref,__self,__source 這4個屬性中的任何一個,都是不能在 this.props 里訪問到的。從第三個參數(shù)開始,傳入的參數(shù)都會合并為 children 屬性,如果只有一個,那么 children 就是第三個元素,如果超過一個,那么這些元素就會合并成一個 children 數(shù)組。
第二件是初始化 defaultProps,我們可以發(fā)現(xiàn),defaultProps 是通過 type 來初始化的,我們在上面也說過,對于 react 組件來說,type 是 React Element 所屬的類,所以可以通過 type 取到該類的 defaultProps(默認屬性)。這里還有一點需要注意,如果我們把某個屬性的值定義成 undefined,那么這個屬性也會使用默認屬性,但是定義成 null 就不會使用默認屬性。
下面是圖解:
4. 創(chuàng)建Virtual DOM樹有了上面的作為基礎(chǔ),那創(chuàng)建 Virtual DOM 就很簡單了。整個 Virtual DOM 就是一個巨大的對象。
比如我們有這么一個 App:
App:Header:
List:
text logo
通過上面的了解到的 React Element 創(chuàng)建方式,我們不難知道,生成的對應(yīng)的 Virtual DOM 應(yīng)該是類似于這樣的:
需要注意的是,這些元素并不是真實的 DOM 元素, 它們只是一些對象,而且我們可以看到 React 組件實際上是概念上的形態(tài),最終還是會生成原生的虛擬 DOM 對象。當這些對象上的數(shù)據(jù)發(fā)生變化時,通過打 patch 把變化同步到真實的 DOM 上去。
目前我們可以認為 Virtual DOM 就是這樣的一種形態(tài),但是實際上,并沒有這么簡單,這只是最基本的樣子,在后續(xù)的文章中我會帶大家一起看看更高級的形態(tài)。
iKcamp原創(chuàng)新書《移動Web前端高效開發(fā)實戰(zhàn)》已在亞馬遜、京東、當當開售。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/85052.html
摘要:當正在更新使用渲染的元素列表時,它默認使用就地更新的策略。如果數(shù)據(jù)項的順序被改變,將不會移動。避免對節(jié)點就地復(fù)用需要修改的節(jié)點位置沒有改變,是內(nèi)容更新了,這雖然提高了復(fù)用性能,但是往往在復(fù)雜的表單會導(dǎo)致狀態(tài)出現(xiàn)錯位。 當 Vue 正在更新使用 v-for 渲染的元素列表時,它默認使用就地更新的策略。如果數(shù)據(jù)項的順序被改變,Vue 將不會移動 DOM。 元素來匹配數(shù)據(jù)項的順序,而是就地更...
摘要:具體而言,就是每次數(shù)據(jù)發(fā)生變化,就重新執(zhí)行一次整體渲染。而給出了解決方案,就是。由于只關(guān)注,通過閱讀兩個庫的源碼,對于的定位有了更深一步的理解。第二個而且,技術(shù)本身不是目的,能夠更好地解決問題才是王道嘛。 前言 React 好像已經(jīng)火了很久很久,以致于我們對于 Virtual DOM 這個詞都已經(jīng)很熟悉了,網(wǎng)上也有非常多的介紹 React、Virtual DOM 的文章。但是直到前不久...
摘要:二原理每個都有兩個,一個是新的,一個是原來的。三實現(xiàn)過程四算法的理解與實現(xiàn)本質(zhì)上就是在和之間做了一個緩存。將差異的應(yīng)用到真正的樹上對真實上的樹進行深度優(yōu)先遍歷,在所有的差異列表中找出當前遍歷的節(jié)點差異,然后根據(jù)不同進行操作。 React Virtual DOM 一、概念 在react中,對于每個DOM對象都有一個相應(yīng)的虛擬DOM對象,相當于DOM對象的輕量級副本 由于是Virtual...
摘要:前言的基本概念組件的構(gòu)建方法以及高級用法這背后的一切如何運轉(zhuǎn)深入內(nèi)部的實現(xiàn)機制和原理初探源碼代碼組織結(jié)構(gòu)包含一系列的工具方法插件包含一系列同構(gòu)方法包含一些公用或常用方法如等包含一些測試方法等包含一些邊界錯誤的測試用例是代碼的核心部分它包含了 前言 React的基本概念,API,組件的構(gòu)建方法以及高級用法,這背后的一切如何運轉(zhuǎn),深入Virtual DOM內(nèi)部的實現(xiàn)機制和原理. 初探Rea...
摘要:模型模型負責底層框架的構(gòu)建工作它擁有一整套的標簽并負責虛擬節(jié)點及其屬性的構(gòu)建更新刪除等工作其實構(gòu)建一套簡易模型并不復(fù)雜它只需要具備一個標簽所需的基本元素即可標簽名屬性樣式子節(jié)點唯一標識中的節(jié)點稱為它分為種類型其中又分為和創(chuàng)建元素輸入輸出通過 Virtual DOM模型 1.Virtual DOM模型負責Virtual DOM底層框架的構(gòu)建工作,它擁有一整套的Virtual DOM標簽,...
閱讀 2116·2023-04-25 19:03
閱讀 1269·2021-10-14 09:42
閱讀 3455·2021-09-22 15:16
閱讀 1029·2021-09-10 10:51
閱讀 1659·2021-09-06 15:00
閱讀 2434·2019-08-30 15:55
閱讀 516·2019-08-29 16:22
閱讀 916·2019-08-26 13:49