第 2 章:JSX 深度解析
在上一章中,我們簡要提到了 JSX。這一章我們將更深入地探討 JSX 的語法、特性以及它在 React 中的核心作用。
2.1 JSX 是什麼?它與 HTML 的關係
JSX (JavaScript XML) 是 JavaScript 的一個語法擴展。它看起來非常像 HTML,但實際上它最終會被轉換為常規的 JavaScript 物件。React 推薦使用 JSX 來描述 UI 的結構和外觀,因為它比直接呼叫 React.createElement()
更直觀、更易讀。
與 HTML 的主要區別:
- 屬性命名:由於 JSX 本質上是 JavaScript,所以一些 HTML 屬性名稱與 JavaScript 的保留關鍵字衝突。為了解決這個問題,React DOM 使用駝峰式命名 (camelCase) 來命名屬性。
- 例如,HTML 的
class
屬性在 JSX 中寫作className
。 - HTML 的
for
屬性 (用於<label>
) 在 JSX 中寫作htmlFor
。 - HTML 的
tabindex
屬性在 JSX 中寫作tabIndex
。 - 以
data-
或aria-
開頭的自訂屬性和 ARIA 屬性則保持與 HTML 一致。
- 例如,HTML 的
- 樣式 (Style):在 JSX 中,
style
屬性接受一個 JavaScript 物件,而不是像 HTML 中那樣接受一個 CSS 字串。物件的屬性名也使用駝峰式命名。const divStyle = { color: 'blue', backgroundImage: 'url(' + imgUrl + ')', // 字串值 fontSize: 16, // 數字值通常會被 React 自動加上 'px' paddingLeft: '10px' // 也可以明確指定單位 }; function HelloWorldComponent() { return <div style={divStyle}>Hello World!</div>; }
- 註解:在 JSX 中使用 JavaScript 註解,需要用
{}
包裹。const element = ( <div> {/* 這是一個單行註解 */} <h1>Hello!</h1> {/* 這是一個 多行註解 */} </div> );
- 自閉合標籤:對於沒有子元素的標籤,必須以
/>
結尾,類似 XML。例如<img />
或<br />
。 根元素:JSX 表達式必須有一個單一的根元素。如果有多個同級元素,需要用一個父標籤 (如
<div>
) 包裹它們,或者使用 React Fragment (<React.Fragment>
或更簡潔的<></>
)。// 錯誤的!相鄰的 JSX 元素必須被包裹在一個閉合標籤中。 // const element = ( // <h1>Hello</h1> // <h2>World</h2> // ); // 正確的:使用 div 包裹 const elementWithDiv = ( <div> <h1>Hello</h1> <h2>World</h2> </div> ); // 正確的:使用 React Fragment const elementWithFragment = ( <React.Fragment> <h1>Hello</h1> <h2>World</h2> </React.Fragment> ); // 正確的:使用 Fragment 的簡寫語法 const elementWithShortFragment = ( <> <h1>Hello</h1> <h2>World</h2> </> );
2.2 在 JSX 中嵌入 JavaScript 表達式
您可以在 JSX 中使用大括號 {}
來嵌入任何有效的 JavaScript 表達式。
function Greeting(props) {
const name = props.name;
const user = { firstName: 'Harper', lastName: 'Perez' };
return (
<div>
<h1>Hello, {name}!</h1> {/* 嵌入變數 */}
<h2>It is {new Date().toLocaleTimeString()}.</h2> {/* 嵌入函式呼叫 */}
<p>2 + 2 = {2 + 2}</p> {/* 嵌入算術運算 */}
<p>Full name: {user.firstName + ' ' + user.lastName}</p> {/* 嵌入物件屬性 */}
</div>
);
}
在 {}
中,您可以放置變數、函式呼叫、算術運算、物件屬性存取等。if
語句和 for
迴圈不是 JavaScript 表達式,所以不能直接在 JSX 中使用,但您可以在 JSX 外部的 JavaScript 程式碼中使用它們,或者使用三元運算子等表達式形式。
2.3 JSX 也是一個表達式
經過編譯後,JSX 表達式會變成普通的 JavaScript 函式呼叫,並且其運算結果是 JavaScript 物件 (React 元素)。這意味著您可以在 if
語句和 for
迴圈中使用 JSX,將 JSX 賦值給變數,作為引數傳入函式,以及從函式中回傳 JSX。
function getGreeting(user) {
if (user) {
return <h1>Hello, {user.name}!</h1>; // 從函式回傳 JSX
}
return <h1>Hello, Stranger.</h1>;
}
const isLoggedIn = true;
const greetingElement = getGreeting(isLoggedIn ? { name: 'Alice' } : null); // 將 JSX 賦值給變數
function App() {
let uiElement;
if (isLoggedIn) {
uiElement = <p>Welcome back!</p>; // 在 if 中使用 JSX
} else {
uiElement = <p>Please sign up.</p>;
}
return (
<div>
{greetingElement}
{uiElement}
</div>
);
}
2.4 指定 JSX 屬性
您可以使用引號來將字串字面量指定為屬性:
const element = <div tabIndex="0">Focusable Div</div>;
您也可以使用大括號 {}
來將 JavaScript 表達式嵌入到屬性中:
const avatarUrl = "https://example.com/avatar.jpg";
const element = <img src={avatarUrl} alt="User Avatar" />;
const isDisabled = true;
const button = <button disabled={isDisabled}>Submit</button>;
注意:當使用大括號將 JavaScript 表達式嵌入到屬性中時,不要在大括號外面再加引號。您應該要麼使用引號 ( for 字串值),要麼使用大括號 ( for 表達式),但不能同時使用。
如果屬性值為 true
,可以省略其值,JSX 會將其預設為 true
。
<MyInput autocomplete /> // 等同於 <MyInput autocomplete={true} />
如果屬性值為 false
,則必須使用大括號表達式。
<MyInput spellCheck={false} />
2.5 JSX 中的子元素
如果一個標籤是空的 (沒有子元素),您可以像 XML 一樣,使用 />
來立即閉合它:
const element = <img src={user.avatarUrl} />;
JSX 標籤也可以包含子元素:
const element = (
<div>
<h1>Hello!</h1>
<h2>Good to see you here.</h2>
</div>
);
子元素可以是字串字面量、JSX 元素,或者任何用 {}
包裹的 JavaScript 表達式。React 元件也可以作為子元素。
2.6 JSX 防止注入攻擊
在 JSX 中嵌入使用者輸入是安全的。預設情況下,React DOM 在渲染所有嵌入 JSX 的值之前,都會對其進行轉義 (escapes)。因此,它可以確保您的應用程式不會受到 XSS (cross-site-scripting) 攻擊。所有內容在渲染之前都會被轉換為字串。
const title = response.potentiallyMaliciousInput; // 例如:"<script>alert('hack')</script>"
// 這是安全的:
const element = <h1>{title}</h1>;
// React DOM 會將 title 轉義,最終渲染的 HTML 會是:
// <h1><script>alert('hack')</script></h1>
// 而不是執行腳本。
2.7 JSX 代表物件 (Babel 編譯)
Babel (一個 JavaScript 編譯器) 會將 JSX 轉換為對 React.createElement()
的呼叫。
以下兩個範例是完全等效的:
// 使用 JSX
const elementJsx = (
<h1 className="greeting">
Hello, world!
</h1>
);
// 不使用 JSX,直接呼叫 React.createElement()
const elementCreateElement = React.createElement(
'h1',
{className: 'greeting'},
'Hello, world!'
);
React.createElement()
會執行一些檢查來幫助您編寫無錯的程式碼,但實際上它會建立一個像這樣的物件 (簡化版):
// 注意:這是簡化後的結構
const elementObject = {
type: 'h1',
props: {
className: 'greeting',
children: 'Hello, world!'
}
};
這些物件被稱為 "React 元素"。您可以把它們看作是您想在螢幕上看到的 UI 的描述。React 會讀取這些物件,並使用它們來建構 DOM 以及保持 DOM 的更新。
理解 JSX 最終會被轉換成 React.createElement()
呼叫是很重要的,這意味著 React 函式庫必須始終在您的 JSX 程式碼的作用域內。如果您在一個檔案中使用了 JSX,就必須匯入 React (儘管在現代的 React 版本和設定中,有時這一步驟是隱含的,但了解其原理很重要)。
import React from 'react'; // 必須匯入 React 才能使用 JSX (在舊版或特定配置中)
function MyComponent() {
return <div>Hello JSX!</div>;
}
在 Vite 等現代建置工具中,通常會配置 Babel 或類似的轉換器來自動處理 JSX 到 React.createElement
(或其在 React 17+ 中的新轉換形式 _jsx
) 的轉換,並且可能不再需要在每個檔案中顯式 import React
(如果使用了新的 JSX 轉換)。但 @memos
專案的 main.tsx
中明確 import React from 'react'
,這是一個好的習慣,也確保了相容性。
JSX 是 React 的基石之一,理解它對於掌握 React 至關重要。