(2024/04/06更新) 因應React在18後更新了許多不同的語法,更新後的教學之後將陸續放在 新的blog 中,歡迎讀者到該處閱讀,我依然會回覆這邊的提問
在前五天的原生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。
在React 16之前,Babel遇到JSX語法的時候會自動幫我們轉成React.createElement
。以下是JSX的一些規定:
例如,下方是合法的語法,會跟昨天的Hello world一樣:
import React from 'react';
import ReactDOM from 'react-dom';
const menuFactory = () =>{
return <div>hello world!</div>;
}
ReactDOM.render(
menuFactory(),
document.getElementById('root')
);
意思是下方這樣是錯誤的:
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')
);
效果和剛剛一模一樣。
以下面這個範例而言 :
import React from 'react';
import ReactDOM from 'react-dom';
ReactDOM.render(
<div> { 1 + 1 } </div>,
document.getElementById('root')
);
執行結果是:
請注意如果要在{}
中使用字串,就必須要使用"文字"
或是'文字'
,因為{}
中就等於是Javascript語法了。相反的像true
或false
這種布林值就不能加上引號。
//這是正確的寫法
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')
);
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語法,裡面的括號則表示物件型態。
在下面的程式中,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')
);
在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')
);
例如,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')
);
有關其他輸入元素的控制方法,我們會在後面的章節提到。
使用JSX,再讓babel轉成React.createElement
的做法在開發上雖然比以前方便很多,但也遇到了兩個問題:
React.createElement
,每次在React元件的JS檔中都要import React from 'react'
。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"}]
]
}