在講解虛擬DOM(Virtual DOM)以前能夠先了解下真實的DOM是什麼html
Virtual DOM 本質上是JavaScript對象,是對真實DOM的的一種描述方式。
即JS對象模擬的DOM結構,將DOM變化的對比放在JS層來作。讓UI渲染變成 UI = f(data)的形式。
示例:HTML裏的一段標籤文本前端
<ul id="list">
<li class="item">Item1</li>
<li class="item">Item2</li>
</ul>
複製代碼
用虛擬DOM結構能夠表示爲:react
{
tag: "ul",
attrs: {
id: "list"
},
children: [
{
tag: "li",
attrs: { className: "item" },
children: ["Item1"]
}, {
tag: "li",
attrs: { className: "item" },
children: ["Item2"]
}
]
}
複製代碼
tag是標籤名稱
attrs是標籤屬性組成的對象
children是包含在標籤內的子標籤git
因此,Virtual DOM 實際上是一個JS對象,相似JSON格式,React中就是將JSX元素的名稱、屬性和內容做爲對象及其屬性來建立Virtual DOM。github
不過,針對單一場景的性能優化,專門針對該場景的js進行極致優化 比 Virtual DOM 方案更適用。算法
在React開發中,Virtual DOM 元素便是React 元素,常常會使用JSX來寫React元素,好比
簡單的api
<div>Hello</div>
複製代碼
稍微複雜一些的瀏覽器
<div className="divStyleName" onClick={(e) => console.log('點擊了')}>
Hello
<img src="pic_url">img</img>
<div>div2</div>
<div></div>
</div>;
複製代碼
不過,在React中不是必需要使用JSX的。
由於其實 JSX 元素只是調用 React.createElement(component, props, ...children) 的語法糖。其本質仍是JavaScript,全部的JSX語法最終都會被轉換成對這個方法的調用。所以,使用 JSX 能夠完成的任何事情均可以經過純 JavaScript 完成。
同時,瀏覽器是不能直接讀取JSX,爲了使瀏覽器可以讀取JSX,須要在項目構建環境中配置有關 JSX 編譯。好比 讓 Babel 這樣的 JSX 轉換器將 JSX語句 轉換爲 目標 js 代碼,再將其傳給瀏覽器,JS引擎才能成功執行語句。性能優化
用純JavaScript來寫上面的JSX示例(通過babel轉譯)服務器
React.createElement("div", {
className: "divStyleName",
onClick: e => console.log('點擊了')
}, "Hello", React.createElement("img", {
src: "pic_url"
}, "img"), React.createElement("div", null, "div2"), React.createElement("div", null));
複製代碼
從最層的React.createElement分析起
第一個參數值是組件名
第二個參數值是一個對象,由組件的屬性構成
第三到後面的一系列參數值,就是子元素了。若是子元素依然是JSX元素,就繼續經過React.createElement來建立對象
React.createElement建立的React元素就是虛擬DOM樹的元素,因而,就構成出一個虛擬DOM樹了。
這裏在瀏覽器窗口中看下上面所述內容的結果
注意這裏的type的值是"div",是同名的原生 html 的div標籤名再來自定義一個組件,名爲HelloComponent,JSX實現爲
class HelloComponent extends React.Component {
render() {
return <div>Hello {this.props.toWhat}</div>;
}
}
<HelloComponent toWhat="World" />
複製代碼
Babel轉譯後對應的js代碼爲
class HelloComponent extends React.Component {
render() {
return React.createElement("div", null, "Hello ", this.props.toWhat);
}
}
React.createElement(HelloComponent, {
toWhat: "World"
});
複製代碼
而後在瀏覽器窗口看下
注意這裏type的值是一個類構造函數了
這是babel在編譯時會判斷JSX中組件的首字母:
因此自定義組件名稱必需要首字母大寫。
下面就來研究下React.createElement(component, props, ...children)具體的實現 以及 是如何來構建Virtual DOM的吧~
要開始看React 源碼啦~
由於是React.createElement,因此天然就找到React.js文件,查看其createElement方法。 而後就發現其實的調用了ReactElement.js文件中的createElement方法,返回一個ReactElement對象
/** * Create and return a new ReactElement of the given type. * See https://reactjs.org/docs/react-api.html#createelement */
export function createElement(type, config, children) {
let propName;
// Reserved names are extracted
const props = {};
let key = null;
let ref = null;
let self = null;
let source = null;
/* ... 根據入參各類邏輯判斷賦值ref、source、self、props等 ... */
return ReactElement(
type,
key,
ref,
self,
source,
ReactCurrentOwner.current,
props,
);
}
複製代碼
再看下ReactElement方法的具體實現
const ReactElement = function(type, key, ref, self, source, owner, props) {
const element = {
// This tag allows us to uniquely identify this as a React Element
$$typeof: REACT_ELEMENT_TYPE,
// Built-in properties that belong on the element
type: type,
key: key,
ref: ref,
props: props,
// Record the component responsible for creating this element.
_owner: owner,
};
if (__DEV__) {
// 天然是給element的各個屬性賦值了
}
return element;
};
複製代碼
返回的是是一個JS對象,在React中稱爲ReactElement對象:
{
$$typeof: REACT_ELEMENT_TYPE,
type: type,
key: key,
ref: ref,
props: props,
_owner: owner,
}
複製代碼
結構正是上面在瀏覽器窗口中看到的React.createElement的結果的結構。
這裏簡單介紹下ReactElement對象的屬性及其含義。
瞭解了單個ReactElement對象後,前面提到的示例層層調用React.createElement,就能夠建立出一個Virtual DOM Tree了,即生成原生的虛擬 DOM 對象。
React.createElement("div", {
className: "divStyleName",
onClick: e => console.log('點擊了')
}, "Hello", React.createElement("img", {
src: "pic_url"
}, "img"), React.createElement("div", null, "div2"), React.createElement("div", null));
複製代碼
在線Babel編輯器編寫查看更多JSX轉換成JavaScript的示例