JSX 是一種語法,並不是 React 中的內容,時下接入 JSX 語法的框架越來越多,但與之緣分最深的仍然是 React。本節來講一下 React 是如何搖身一變成為 DOM 的。
我們平時在寫React時會用 JSX 來描述元件的內容,例如下面的程式碼中,render 方法 return 的內容就是 JSX 程式碼。
class App extends React.Component {
render() {
return (
<div className="App">
<h1 className="title">I am the title</h1>
<p className="content">I am the content</p>
</div>
);
}
}
我們考慮以下三個問題:
這一節我們就將這三個問題一一解答。
JSX 到底是什麼,我們先來看看 React 官網給出的一段定義:
JSX 是 JavaScript 的一種語法擴充套件,它和模板語言很接近,但是它充分具備 JavaScript 的能力。
「語法擴充套件」這一點在理解上幾乎不會產生歧義,不過「它充分具備 JavaScript 的能力」這句,卻總讓人摸不著頭腦,JSX 和 JS 怎麼看也不像是一路人啊?這就引出了「JSX 語法是如何在 JavaScript 中生效的」這個問題。
JSX 是 JavaScript 的擴充套件,而不是 JavaScript 的某個版本,因此瀏覽器並不會天然支援,那麼 JSX 是如何在 JavaScript 中生效的呢?
React 官網是這樣的解釋的:
JSX 會被編譯為 React.createElement(), React.createElement() 將返回一個叫作「React Element」的 JS 物件。
那麼 JSX 如何轉換成 React.createElement() 的呢?答案就是通過 babel 轉換。
我們直接開啟 babel playground 來寫一段 JSX 程式碼看一下 babel 轉換後的結果。![image-20231204112041472](/Users/jiuyuezhang/Library/Application Support/typora-user-images/image-20231204112041472.png)
可以看到 JSX 程式碼都被轉換成了 React.createElement 呼叫。
接下來我們總結一下來回答標題提到的兩個問題。
JSX 是 JavaScript 的擴充套件,不是 JavaScipt 的某個版本,需要通過 Babel 進行轉換成 JavaScript 程式碼。
JSX 會被 babel 轉換為 React.CreateElement(...) 呼叫的形式,執行後返回的結果是一個物件。
從上一節我們知道 JSX 等價於一次 React.createElement 呼叫,那麼 React 官方為什麼不直接引導我們用 React.createElement 來建立元素呢?
在實際功能效果一致的前提下,JSX 程式碼層次分明、巢狀關係清晰;而 React.createElement 程式碼則給人一種非常混亂的「雜糅感」,這樣的程式碼不僅讀起來不友好,寫起來也費勁。
JSX 語法糖允許前端開發者使用我們最為熟悉的類 HTML 標籤語法來建立虛擬 DOM,在降低學習成本的同時,也提升了研發效率與研發體驗。
我們知道 JSX 經過babel轉換後會變成 React.createElement(...)
的形式,接下來我們就來一起探討一下 React.createElement(...)
是如何工作的?
我們先來看看方法的入參:
export function createElement(type, config, children)
createElement 有 3 個入參,這 3 個入參囊括了 React 建立一個元素所需要知道的全部資訊。
舉個例子:
<ul className="list">
<li key="1">1</li>
<li key="2">2</li>
</ul>
經過 Babel 轉換後的形式為:
注意:從第三個入參開始往後,傳入的引數都是 children
React.createElement("ul", {
// 傳入屬性鍵值對
className: "list"
}, React.createElement("li", {
key: "1"
}, "1"), React.createElement("li", {
key: "2"
}, "2"));
下面的程式碼是 React.createElement(...) 呼叫的返回值格式。
注意:這是 fiber節點之前的每個節點的格式。
const ReactElement = function(type, key, ref, self, source, owner, props) {
const element = {
// REACT_ELEMENT_TYPE是一個常數,用來標識該物件是一個ReactElement
$$typeof: REACT_ELEMENT_TYPE,
// 內建屬性賦值
type: type,
key: key,
ref: ref,
props: props,
// 記錄創造該元素的元件
_owner: owner,
};
//
if (__DEV__) {
// 這裡是一些針對 __DEV__ 環境下的處理,對於大家理解主要邏輯意義不大,此處我直接省略掉,以免混淆視聽
}
return element;
};
舉個例子
const AppJSX = (<div className="App">
<h1 className="title">I am the title</h1>
<p className="content">I am the content</p>
</div>)
console.log(AppJSX)
輸出為:
這個 ReactElement 物件範例,本質上是以 JavaScript 物件形式存在的對 DOM 的描述,也就是老生常談的「虛擬 DOM」(準確地說,是虛擬 DOM 中的一個節點。
既然是「虛擬 DOM」,那就意味著和渲染到頁面上的真實 DOM 之間還有一些距離,這個「距離」,就是由大家喜聞樂見的ReactDOM.render方法來填補的。
在每一個 React 專案的入口檔案中,都少不了對 React.render 函數的呼叫。下面我簡單介紹下 ReactDOM.render 方法的入參規則:
複製程式碼
ReactDOM.render(
// 需要渲染的元素(ReactElement)
element,
// 元素掛載的目標容器(一個真實DOM)
container,
// 回撥函數,可選引數,可以用來處理渲染結束後的邏輯
[callback]
)
ReactDOM.render 方法可以接收 3 個引數,其中第二個引數就是一個真實的 DOM 節點,這個真實的 DOM 節點充當「容器」的角色,React 元素最終會被渲染到這個「容器」裡面去。比如,範例中的 App 元件,它對應的 render 呼叫是這樣的:
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
注意,這個真實 DOM 一定是確實存在的。比如,在 App 元件對應的 index.html 中,已經提前預置 了 id 為 root 的根節點:
<body>
<div id="root"></div>
</body>
-------------------------------------------
個性簽名:夢想不只是夢與想
如果您覺得這篇文章哪個地方不恰當甚至有錯誤的話,麻煩告訴一下博主哦,感激不盡。
如果您覺得這篇文章對你有一點小小的幫助的話,希望能在右下角點個「推薦」哦。