iT邦幫忙

第 12 屆 iT 邦幫忙鐵人賽

DAY 4
1
Modern Web

I Want To Know React系列 第 4

I Want To Know React - JSX 語法

回顧 JSX

上一個篇章:初探 JSX 中,我們了解到 JSX 是一種 JavaScript 的擴充語法,支援在 JavaScript 中撰寫 HTML-like/XML-like 的程式。JSX 的功能與 HTML 相似,都是用來定義畫面上的 element 的,而這些 JSX 產生的 element 會在 React render 的階段被顯示到畫面上。

語法上, JSX 與 HTML 也即為相似,同樣支援 children、attribute 等語法:

const element = <h1>Hello, world!</h1>;

// JSX can have children elements
<div><h1>Hello, Henry</h1></div>;

// JSX can have attribute and can use close tag
<img src="https://image.shutterstock.com/image-photo/bright-spring-view-cameo-island-260nw-1048185397.jpg" alt="Oops" className="greeting-picture" />

除此之外,JSX 實際上就是 React.createElement() 的語法糖,而 createElement() 執行後則會產出一種被稱為 React element 的 JavaScript object:

// This is a JSX element
const jsxElement = (
  <h1 className="greeting">
    Hello, world!
  </h1>
);

// it equals to this
const jsElement = React.createElement(
  'h1',
  {className: 'greeting'},
  'Hello, world!'
);

// and the truth is, React elements are just JavaScript Objects
console.log(jsxElment);
// {
//   type: 'h1',
//   props: {
//     className: 'greeting',
//     children: 'Hello, world!'
//   }
//   ...
// };

到這邊為止回顧了 JSX 的功能、語法與內部原理。JSX 的語法雖說與 HTML 相仿,但本質上卻更接近 JavaScript,因此在了解語法細節前,應該將 JSX 就是 JavaScript 的變形這點銘記在心。如果還沒有讀過上一篇的讀者,還是推薦可以先看過之後再繼續閱讀此章節。

接下來就讓我們深入了解 JSX 的語法吧!

JSX 檔案規則

JSX 檔案建議以 .jsx 作為副檔名,以跟一般的 .js 檔做區分。

除此之外,如上篇所提,因為 JSX 其實轉譯後就會變成 React 提供的函式執行:createElement(),因此使用 JSX 時,需要 import React 才可正常使用。

import React from 'react';

// some JSX here
const element = <h1>Hello, world!</h1>;

JSX element 語法

JSX 的語法與 HTML 相似

JSX 定義 element 的語法與一般 HTML 語法類似:

  • 一樣用 <tag> 來表示一個 element
  • 一樣可以有 children element
  • 一樣可以有 attribute
  • 一樣可以在 element children 為空時直接 close tag
// JSX can have children elements
<div><h1>Hello, Henry</h1></div>;

// JSX can use close tag
<img src="https://image.shutterstock.com/image-photo/bright-spring-view-cameo-island-260nw-1048185397.jpg" alt="Oops" className="greeting-picture" />

可以把 JSX element 當作 JavaScript object 使用

JSX 執行後會變成 JavaScript object,因此我們可以對 JSX element 做任何能對 JavaScript object 能做的事情,包括:

  • 取值、賦值
  • 取 property
  • ...etc
// you can assign JSX value to variables
const element = <div><h1>Hello, Henry</h1></div>;

// you can log JSX to see it's value
console.log(element);
// {$$typeof: Symbol(react.element), type: "div", key: null, ref: null, props: Object, …}

// you can retrieve JSX properties
console.log(element.type)
// "div"

跨越多行時可以用 Group Operator 包起來

如果 element 跨越多行,只要在 element 外加上 () 即可,這個是 JavaScript 原生的語法 Group Operator 的功能:

const element = (
  <div>
    <h1>Hello!</h1>
    <h2>Henry</h2>
  </div>
);

console.log(element);
// {$$typeof: Symbol(react.element), type: "div", key: null, ref: null, props: {…}, …}

一個 JSX Element 代表一個 DOM Element

需要注意的地方是,JSX element 代表一個 DOM 物件,所以只能有一個 root element。如果一個 JSX element 中包含多個 root element 的話,就是不合法的 JSX 語法:

const illegalElement = <div><h1>Hello, Henry</h1></div><span></span>;

console.log(illegalElement);
// Parsing error: Adjacent JSX elements must be wrapped in an enclosing tag. Did you want a JSX fragment <>...</>?

嘗試轉譯為 JavaScript 語法

讓我們來試試看把 JSX element 轉譯回 JavaScript 來加深對於 JSX 的理解吧!

  • 先來試試有 children 的 JSX element:
    • 第一個參數會是 root element 的 type
    • 而 children <h1> <h2> 則被轉譯成 React.createElement() 後被帶入第三個之後的參數
// This jsx element
const jsxElement = (
  <div>
    <h1>Hello!</h1>
    <h2>Henry</h2>
  </div>
);

// is equal to
const jsElement = React.createElement(
  "div",
  null,
  React.createElement("h1", null, "Hello!"),
  React.createElement("h2", null, "Henry")
);

讀者也可以到 Babel 提供的 Playground 中自己試試看。

JSX attribute 語法

如上面介紹所示,JSX attribute 的寫法與一般的 HTML 大同小異,也可支援帶 string

const element = <img src="https://image.shutterstock.com/image-photo/bright-spring-view-cameo-island-260nw-1048185397.jpg" alt="Oops" />

console.log(element);
// {$$typeof: Symbol(react.element), type: "img", key: null, ref: null, props: {src: "https://image.shutterstock.com/image-photo/bright-spring-view-cameo-island-260nw-1048185397.jpg", alt: "Oops"}, …}

以 DOM property 作為 attribute 命名

比較需要注意的地方是,JSX 因為底層原理的關係,其語言特性比起 HTML 還是更接近於 JavaScript,因此其 attribute 名稱是比照 DOM property 命名,而非比照 HTML attribute 的命名。以下舉出一些範例:

  • HTML class attribute 在 JSX 會變成 className
  • HTML tableindex attribute 在 JSX 會變成 tableIndex
const greetCss = 'greet';
const element = <div className={greetCss}>Hi, Henry</div>;

console.log(element);
// $$typeof: Symbol(react.element), type: "div", key: null, ref: null, props: {className: "greet", children: "Hi, Henry"}, …

function 需以 {function} 的方式帶入

另外一點是,如果要在 JSX attribute 中帶入 function,則不能像 HTML 一樣帶 string,需要以 expression {function} 的方式帶入。

這個原因是因為 JSX attribute 轉回 JavaScript 後,attribute value 會被帶進 JavaScript object(JSX element)中,在需要的時候拿出來使用。如果我們是帶 string:"function()" 的話,JavaScript 會認為此 value 是 string 而非 function,因此我們應該要帶入原生的 function 讓 JavaScript 看得懂。

讓我們來看個範例:

const doSomething = () => { /* ... */ };

// This is Wrong
<button onClick="doSomething()">
  Activate Lasers
</button>

// This is Correct
<button onClick={doSomething}>
  Activate Lasers
</button>

嘗試轉譯為 JavaScript 語法

又到了轉譯練習了!

  • 接下試試將有 attribute 的 JSX element 轉譯回 JavaScript:
    • 第一個參數是 root element type <h1>
    • 第二個參數是 attribute 組成的 Object,內容會是 className
// This jsx element
const jsxElement = (
  <button className="greeting" onClick={}>
    Hello, world!
  </button>
);

// is equal to
const jsElement = React.createElement(
  'div',
  { className: 'greeting' },
  'Hello, world!'
);

讀者也可以到 Babel 提供的 Playground 中自己試試看。

JSX expression 語法

JSX element 的 value 部分可替換成 {expression} 以支援任何的 JavaSript Expression (表達式)

  • ?小提醒:JavaScript Expression 與 JavaScript Statement 是不同的東西

    JavaSript Expression (表達式) 代表可以解析/運算為一個值的一段程式碼。數學運算、邏輯運算、function 執行等都算是常見的 Expression。由於 Expression 並不是一句完整的程式,而只是一個小單元,因此不會有 ; 在最後面。

    JavaScript Statement (陳述式) 則代表執行動作。變數宣告與 if {} else {} 都是常見的 Statement ,兩者為不一樣的東西

JSX children 可以接受 expression

JSX 支援在 children 中加入 JSX expression:

const firstName = 'Henry';
const lastName = 'Chang';
const element = <h1>Hello, {formatName(firstName, lastName)}</h1>

function formatName(firstName, lastName) {
  return `${firstName}${lastName}`;
}

console.log(element);
// {$$typeof: Symbol(react.element), type: "h1", key: null, ref: null, props: {children: ["Hello, ", "HenryChang"]}, …}

JSX attribute 可以接受 expression

JSX 支援在 attribute value 中加入 JSX expression:

const imgSrc = 'https://image.shutterstock.com/image-photo/bright-spring-view-cameo-island-260nw-1048185397.jpg';
const imgAlt = 'Oops';
const element = <img src={imgSrc} alt={imgAlt} />

console.log(element);
// {$$typeof: Symbol(react.element), type: "img", key: null, ref: null, props: {src: "https://image.shutterstock.com/image-photo/bright-spring-view-cameo-island-260nw-1048185397.jpg", alt: "Oops"}, …}

JSX Element 本身就是 expression

JSX Element 是 React.createElement() 的語法糖,因此也是 expression。把 JSX Element 加入 JSX expression 中是完全沒問題的:

const childElement = <div>child</div>;

// jsx element itself can be treat as jsx expression
const parentElement = <div>{childElement}</div>;

console.log(parentElement);
// {$$typeof: Symbol(react.element), type: "div", key: null, ref: null, props: {…}, …}

JSX expression 只能接受 expression

需要注意的一點是,JSX expression 如字面上所說,就只能接受 JavaScript expression。如果放入 expression 以外的程式就是不合法的,例如 JSX expression 不接受 statement。

判斷一段程式是否為 expression 的簡單技巧就是想想它是否能當作 function 參數,可以被當作 function 參數的程式就是合法的 expression。

// Legal JSX elements examples
const isShowGreet = false;
const log = () => console.log('log');

const doFunctionJSXElement = (
	<div>{log()}</div>
);

const ternaryJsxElement = (
  <h1 className="greeting">{isShowGreet && 'Hello, world!'}</h1>
);

const assignJsxElement = (
  <h1 className="greeting">{isShowGreet = true}</h1>
);
// Illegal JSX elements examples
// They are illegal because codes inside {} are statements, not expressions
const isShowGreet = false;

const conditionJsxElement = (
  <h1 className="greeting">
    {
      if (isShowGreet) {
        // ...
      }
    }
  </h1>
);
// Parsing error: Unexpected token "if"
// eslint: must be an expression

const declarationJsxElement = (
  <h1 className="greeting">
    {
      let greet = 'Hello, world!';
    }
  </h1>
);
// Parsing error: Unexpected reserved word 'let'

const semicolonJsxElement = (
  <h1 className="greeting">
    {
      isShowGreet = true;
    }
  </h1>
);
// Parsing error: Unexpected token, expected "}"

嘗試轉譯為 JavaScript 語法

最後的轉譯練習了!

  • 接下試試將有 expression 的 JSX element jsxParent 轉譯回 JavaScript 吧:
    • 第一個參數是 root element type <div>
    • 第二個參數是 attribute 組成的 Object,內容會是 className,而 value 的部分就會是 {expression} 內的 parentClass
    • 第三個以後的變數都是是 children,因此 expression getParentWord()jsxChildren 就被原封不動的帶進第三與第四個參數了
const parentClass = 'parent';
const getParentWord = () => 'This is parent';
const jsxChildren = <div>This is child</div>;

// This jsx element
const jsxParent = (
  <div className={parentClass}>
		{getParentWord()}
		{jsxChildren}
  </div>
);

// is equal to
const jsParent = React.createElement(
  "div",
  { className: parentClass },
  getParentWord(),
  jsChild,
);

轉譯成 JavaScript 後就不難了解為何 {} 中只能帶入 expression 了,因為轉譯後的 JavaScript property 與 JavaScript argument 同樣只能帶入 expression 啊!

讀者也可以到 Babel 提供的 Playground 中自己試試看。

小結

在這個章節中,我們介紹了這些 JSX 的語法:

  • JSX element 語法
  • JSX attribute 語法
  • JSX expression 語法

並學習了如何轉換成 JavaScript。下一篇開始將學習如何將 React Element render 出來。

參考資料


上一篇
I Want To Know React - 初探 JSX
下一篇
I Want To Know React - Render React Element
系列文
I Want To Know React30

尚未有邦友留言

立即登入留言