React JSX
React 提供了稱作 JSX 的語法,作為 JavaScript extension syntax,JSX 是一種跟 HTML/XML 很相似的語法,用來撰寫所謂的 React elements(React 元素)。
JSX 的語法,舉例像是這個簡單的 JavaScript 變數宣告:
const myReactEle = <h1>Hello, world!</h1>
<h1>Hello, world!</h1> 這一段 code 就是所謂的 JSX,注意它不是字串喔(前後沒有引號包著),而 myReactEle 這個變數的值則是一個 React element。
JSX 是 React 的樣板語言(template language),你可以把 JSX 想像成是 JavaScript 的 HTML DOM literal 表示法,這樣你就會更好理解 JSX 了!
為什麼需要 JSX?
傳統來說,我們常會將 JavaScript 中會用到的 HTML template 散佈在各處,可能是放在 <script> tag 裡面,或放在幾個 template 文件檔案中,然後再用 JavaScript 將這些 template string 取出來 parse 使用,嚴格的執行邏輯(JavaScript)、樣式(CSS)、和內容(HTML)分離(separation)。
但 React 的想法則是相反,React 以元件(components)為中心思考,認為一個元件的顯示邏輯(HTML)和 UI 運作邏輯(JavaScript)應該是緊密相關,應該綁在一起的才對,所以有了 JSX。
JSX 第一眼看你可能會覺得怪,但當你開始習慣 React 後,你會覺得 HTML 和 JavaScript 放在一起其實沒什麼問題,反而有好處,讓你一眼看你的 component code 就能夠有完整的資訊,可以知道這個 component 的長相、結構和邏輯是怎樣子的,code 也好維護。
JSX 的背後原理
JSX 的背後其實就只是 JavaScript 而已,因為 JSX 不是瀏覽器認識的語法,所以我們還會需要透過編譯工具(如 Vite、Babel)來將 JSX 轉成單純的 JavaScript 程式碼 - 轉成 React library 的 React.createElement() function calls。
例如以這個例子 JSX:
const element = <h1>Hello, world!</h1>
透過編譯後會轉成像這樣子的 JavaScript code:
const element = React.createElement('h1', null, 'Hello, world!')
看到這邊有沒有更感受到 JSX 帶來的好處?除了可以很直觀的用類 HTML 的語法來描述介面,同時你也不用寫一堆雜亂又冗長的 React.createElement( React.createElement( React.createElement( ... ) ) ) code。
JSX 語法 (Syntax)
先簡單說,JSX 就是 HTML/XML + JavaScript。
除了 HTML,你可以在 JSX 中使用任何 JavaScript expression - 例如 2 + 2、user.firstName、formatName(user) - 在兩個大括號 { } 之間。
例如:
const element = <h1>Hello, {formatName(user)}!</h1>
其中 {formatName(user)} 的地方就會被取代成 formatName function 的返回值。
( ) 不是必要的,只是個好習慣,避免意外錯誤發生。JSX 可以是巢狀的元素(nested elements):
const element = (
<div>
<h1>Hello!</h1>
<h2>Good to see you here.</h2>
</div>
)
就像 XML,如果遇到一個 empty tag,你需要主動 close />:
// 正確
const element = <img src={user.avatarUrl} />;
// 錯誤
const element = <img src={user.avatarUrl}>;
Fragment - 多個元素不需要外層容器
在 React 中,一個元件只能回傳一個根元素。但有時候你不想多加一個無意義的 <div> 容器,這時可以使用 Fragment。
Fragment 讓你可以將多個元素組合在一起,而不會在 DOM 中增加額外的節點:
// 使用 Fragment 完整語法
import { Fragment } from 'react'
function MyComponent() {
return (
<Fragment>
<h1>標題</h1>
<p>內容</p>
</Fragment>
)
}
更常見的是使用簡寫語法 <>...</>:
// 使用 Fragment 簡寫語法(推薦)
function MyComponent() {
return (
<>
<h1>標題</h1>
<p>內容</p>
</>
)
}
什麼時候需要用完整的 Fragment 語法?
當你需要為 Fragment 加上 key 屬性時(例如在列表渲染中),就必須使用完整語法:
function Glossary(props) {
return (
<dl>
{props.items.map((item) => (
// 需要 key 時,使用完整的 Fragment 語法
<Fragment key={item.id}>
<dt>{item.term}</dt>
<dd>{item.description}</dd>
</Fragment>
))}
</dl>
)
}
標籤名稱大小寫的差別 (Tags Naming Convention)
用 JSX 寫 React elements DOM 結構時,如果是一般的 HTML tags 則用小寫(lowercase names)。
例如:
const element = <div />
但如果是 React Component,則 tag 名稱首字用大寫(capital letter)。
例如:
const element = <Welcome name="Sara" />
JSX 是 JavaScript expression
JSX 本質是一個 JavaScript expression,所以你可以用在 if 條件、for 迴圈、指定給變數、當作函數參數或返回值等。
例如:
function getGreeting(user) {
if (user) {
return <h1>Hello, {formatName(user)}!</h1>
}
return <h1>Hello, Stranger.</h1>
}
在 JSX 中使用條件式
因為 { } 中只能放 JavaScript expression,所以在 JSX 你是無法直接使用 if(if 是一個 JavaScript statement)的,但是你可以用 JavaScript 三元運算子。
例如:
function MyComponent(props) {
return <p>Hello {props.someVar ? 'World' : 'Mike'}</p>
}
更多條件渲染的技巧請參考 React 條件渲染。
JSX 的屬性 (attributes)
跟 HTML 一樣 JSX 的 tag 可以有屬性:
const element = <div tabIndex="0"></div>
當使用 " " 包住的屬性值,它的 type 是字串型態(string)。不過你可以用 { } 來給不同的 type 或動態變數的屬性值:
const element = <img src={user.avatarUrl} />
在 JSX 中,如果屬性沒設定值,預設值為 true:
const element = <input type="button" disabled />
// 意思等同於
const element = <input type="button" disabled={true} />
而如果沒設定某個屬性,預設值則為 undefined。
className 與 htmlFor
因為 JSX 底層其實就只是 JavaScript,同時 React 的 API 遵循著 JavaScript DOM API,所以屬性名稱也是按照 JavaScript 原生的語法。
依此原則,舉例如 HTML 中常用到的 class attribute 在 JSX 中則要使用 className;而 for 則是要用 htmlFor。
例如:
const element = <h1 className="title">Hello, world!</h1>
const element = (
<label htmlFor="email">Email</label>
<input id="email" type="email" />
);
Spread Attributes
你可以用 ES6 的 Spread Operator 來方便設定屬性。
例如:
const props = {
foo: 'hello',
bar: 'world',
}
const component = <Component {...props} />
// 等同於
const component = <Component foo="hello" bar="world" />
此外,同一個 tag 後面的屬性值會蓋掉前面相同名稱的屬性值,這特性可以用來做 default values:
const props = { foo: 'default' }
const component = <Component {...props} foo="override" />
// component.props.foo 的值會是 'override'
Inline Styles(CSS 樣式)
JSX 的 inline style 樣式語法跟你用 JavaScript DOM API 是一樣的(camelCased properties)。
例子:
const divStyle = {
color: 'blue',
backgroundColor: '#f0f0f0',
fontSize: '16px',
}
function HelloWorldComponent() {
return <div style={divStyle}>Hello World!</div>
}
注意有兩個括號 {{ }},第一個 {} 是 JSX 語法的 {},而第二個 {} 表示屬性值是一個 JavaScript object:
// 直接在 JSX 中寫 inline style
<div style={{ color: 'red', marginTop: '10px' }}>Hello World!</div>
如果值是數字,JSX 會自動幫你加上 px 單位,若你不想用 px 你就得明確指定單位:
// 值同 10px
<div style={{ height: 10 }}>
Hello World!
</div>
// 明確指定單位 %
<div style={{ height: '10%' }}>
Hello World!
</div>
JSX 註解 (Comments)
你可以用 /* */ 和 // 在 { } 中作為註解。
各種註解寫法的例子:
const element = (
<div>
{/* 這是單行註解 */}
{
// 這也是註解
// 注意:結尾的 } 不能和 // 在同一行
}
{/*
這是多行註解
可以寫很多行
*/}
<p>內容</p>
</div>
)
dangerouslySetInnerHTML
基於安全的考量,React 對於插入 {} 中的字串都會自動幫你做 HTML escape 來避免 XSS(cross-site-scripting)安全攻擊。
例如下方的例子對於 XSS 攻擊是安全的,因為 React 會幫 title 的值做 HTML escape:
const title = response.potentiallyMaliciousInput
// 這是安全的:
const element = <h1>{title}</h1>
如果你想要像使用 innerHTML 一樣可以直接塞入任意 HTML,你可以用 React 提供的 dangerouslySetInnerHTML 這個屬性,它接受一個帶有 __html key 的物件。
像是這樣子使用:
function createMarkup() {
return { __html: 'First · Second' }
}
function MyComponent() {
return <div dangerouslySetInnerHTML={createMarkup()} />
}
dangerouslySetInnerHTML 時要非常小心,確保插入的內容是可信的,否則可能導致 XSS 攻擊。