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 了!

React 用了很多 ES6 的語法,如果你還不熟悉的話,可以到這邊學習 ES6

為什麼需要 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 不是瀏覽器認識的語法,所以我們還會需要透過編譯工具(如 ViteBabel)來將 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 + 2user.firstNameformatName(user) - 在兩個大括號 { } 之間。

例如:

const element = <h1>Hello, {formatName(user)}!</h1>

其中 {formatName(user)} 的地方就會被取代成 formatName function 的返回值。

JSX 最外面的小括號 ( ) 不是必要的,只是個好習慣,避免意外錯誤發生

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

classNamehtmlFor

因為 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 &middot; Second' }
}

function MyComponent() {
  return <div dangerouslySetInnerHTML={createMarkup()} />
}
使用 dangerouslySetInnerHTML 時要非常小心,確保插入的內容是可信的,否則可能導致 XSS 攻擊。