iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 10
1
Modern Web

從比入門再往前一點開始,一直到深入React.js系列 第 10

【Day.10】React入門 - JSX語法

在前五天的原生JS程式碼中,其實還有一個問題。你會發現類似以下的程式碼不斷的在重複:

let menuContainer = document.createElement('div');
menuContainer.setAttribute('class',"menu-container");

//藍色標題
let title = document.createElement('p')
title.setAttribute('class',"menu-title")
title.textContent="Andy Chang的Like";
menuContainer.appendChild(title);

這樣的過程理論上也應該要被模組化包成函式比較好,於是React提供了一個類似的API:

React.createElement('div', undefined, "hello world")

除了第二個參數不知道為啥是undefined外,其他你應該都猜的出來是要做甚麼的。

實際上第一個參數是component,第二個參數是props,第三個參數是children。現在不知道這些是啥沒關係,我們後面會提到

當然,React不只是包了「創造一般元素的功能」進去這個API裡面,有興趣的可以去看看React的source code。

然而這樣還是不夠直覺。

於是就產生了「讓HTML可以直接寫在Javascript的語法」 - JSX

JSX的語法是什麼呢?

在React 16之前,Babel遇到JSX語法的時候會自動幫我們轉成React.createElement。以下是JSX的一些規定:

1. HTML語法可以當作參數、變數值傳遞

例如,下方是合法的語法,會跟昨天的Hello world一樣:

import React from 'react';
import ReactDOM from 'react-dom';

const menuFactory = () =>{
    return <div>hello world!</div>;
}

ReactDOM.render(
    menuFactory(),
   document.getElementById('root')
);

2. 傳遞HTML時,只能傳遞一個標籤元素

意思是下方這樣是錯誤的:

import React from 'react';
import ReactDOM from 'react-dom';

ReactDOM.render(
    (<div>hello world!</div><button>我是按鍵</button>),
   document.getElementById('root')
);

那如果我們想要傳遞多個元素怎麼辦?這個時候就要用一個container把元素包起來:

import React from 'react';
import ReactDOM from 'react-dom';

ReactDOM.render(
    (<div>
        <div>hello world!</div>
        <button>我是按鍵</button>
    </div>),
   document.getElementById('root')
);

但是這樣很容易多出一堆沒有用的container。為了解決這個問題,React提供了一個叫Fragment的元件。

import React, {Fragment} from 'react'; // 注意這裡必須引入Fragment
import ReactDOM from 'react-dom';

ReactDOM.render(
    <Fragment>
        <div>hello world!</div>
        <button>我是按鍵</button>
    </Fragment>,
   document.getElementById('root')
);

實際渲染到畫面上時,React會自己把Fragment去除掉

另外如果嫌Fragment這個單字太長,React也有提供Fragment簡寫的語法:<></>

import React from 'react'; // 注意這裡不用引入Fragment
import ReactDOM from 'react-dom';

ReactDOM.render(
    <>
        <div>hello world!</div>
        <button>我是按鍵</button>
    </>,
   document.getElementById('root')
);

效果和剛剛一模一樣。

3. 可以在 HTML 標籤中利用「{}」寫Javascript 表示式

以下面這個範例而言 :

import React from 'react';
import ReactDOM from 'react-dom';

ReactDOM.render(
    <div> { 1 + 1 } </div>,
   document.getElementById('root')
);

執行結果是:

請注意如果要在{}中使用字串,就必須要使用"文字"或是'文字',因為{}中就等於是Javascript語法了。相反的像truefalse這種布林值就不能加上引號。

4. 「class」屬性變成「className」。

//這是正確的寫法
ReactDOM.render(
    <ul className="menu">
        <li className="menu-item">Like的發問</li>
    </ul>,
   document.getElementById('root')
);
    
//這是錯誤的寫法
ReactDOM.render(
    <ul class="menu">
        <li class="menu-item">Like的發問</li>
    </ul>,
   document.getElementById('root')
);

5. style變為一物件、屬性名稱規則改用駝峰法(用大寫區隔)、屬性的值變成字串

const menuItemStyle={
    marginBottom: "7px",
    paddingLeft: "26px",
    listStyle: "none"
};

ReactDOM.render(
    <ul className="menu">
        <li className="menu-item" style={menuItemStyle}>Like的發問</li>
    </ul>,
   document.getElementById('root')
);

需要特別注意的是直接在標籤中給style值的寫法:

ReactDOM.render(
    <ul className="menu">
        <li className="menu-item" style={{ marginBottom: "7px", paddingLeft: "26px", listStyle: "none"}}
            >Like的發問
        </li>
    </ul>,
   document.getElementById('root')
);

這裡之所以會有兩層大括號,是因為外面那層括號代表style要被賦予的值會是javascript語法,裡面的括號則表示物件型態。

6. 元素Array會被自動展開

在下面的程式中,menuItemWording是一個array,React就自動展開它裡面的元素並顯示。

import React from 'react';
import ReactDOM from 'react-dom';

let menuItemWording=[
    "Like的發問",
    "Like的回答",
    "Like的文章",
    "Like的留言"
];

let menuItemArr = menuItemWording.map(
    (wording) => <li className="menu-item"> {wording}</li>
);

ReactDOM.render(
    <ul className="menu">{menuItemArr}</ul>,
   document.getElementById('root')
);

7. JSX的JS表示式中,可以用布林值比較來決定是否顯示元素

在Javascript中有個特別的布林值比較語法。像是以下code代表如果condition為true時,回傳後面的1

const condition = true;
const data = condition && 1;

console.log(data)
// 印出 1

我們可以將這個語法利用在JSX中,讓程式碼更簡潔。例如在以下程式碼中,你會發現按鍵並沒有被顯示

ReactDOM.render(
    <ul className="menu">
        { false && <button>我是按鍵</button>}
    </ul>,
   document.getElementById('root')
);

換成這個就會顯示了

ReactDOM.render(
    <ul className="menu">
        { true && <button>我是按鍵</button>}
    </ul>,
   document.getElementById('root')
);

也就是剛剛的程式碼其實等於

const menuItemFactory = () =>{
    if(true)
        return <button>我是按鍵</button>;
}

ReactDOM.render(
    <ul className="menu">
        { menuItemFactory()}
    </ul>,
   document.getElementById('root')
);

8. 所有原生的屬性名稱改為駝峰法命名

例如,onclick變成了onClick。

const handleClick = (event) => {
    console.log(event.target.value);
}

ReactDOM.render(
    <ul className="menu">
        <button value={87} onClick={handleClick}>我是按鍵</button>
    </ul>,
   document.getElementById('root')
);

有關其他輸入元素的控制方法,我們會在後面的章節提到。

React 17 之後

使用JSX,再讓babel轉成React.createElement的做法在開發上雖然比以前方便很多,但也遇到了兩個問題:

  1. 為了要讓Babel知道要轉成React.createElement,每次在React元件的JS檔中都要import React from 'react'
  2. 有一些改善效能、簡化的語法會因為React.createElement而不能使用。

為了解決這兩個問題,在2020年推出的React 17中,讓babel能夠在編譯時,只要遇到JSX就會知道要轉成一個新的函式 - jsx()。簡單來說,某天以後我們就不用在開頭一直import React from 'react'了!

jsx('h1', { children: 'Hello world' });

不過在目前(2020/09)的React中,預設還是用原本的編譯方式。如果你現在就想體驗這個功能,要對打包工具新增一些設定,詳情可以參考React官網

// If you are using @babel/preset-react
{
  "presets": [
     // 把@babel/preset-react改成下面這樣
    ["@babel/preset-react", {"runtime": "automatic"}]
  ]
}

上一篇
【Day.09】React入門 - Hello world、React virtual DOM、webpack-dev-server
下一篇
【Day.11】React入門 - function component、props、children
系列文
從比入門再往前一點開始,一直到深入React.js30

尚未有邦友留言

立即登入留言