前面章節雖然我們已經認識了基本的 JSX 的部分,但這一篇我們會更深入的認識了解 JSX,讓我們在開發的時候可以少一點雷點,甚至活用一些技巧。
還記得我們最早前面是怎麼建立 React DOM 的嗎?
忘了也沒關係,我們一開始都是使用 React.createElement
來建立畫面
const app = document.querySelector('#root');
const root = ReactDOM.createRoot(app);
const element = React.createElement('h1', {
children: 'Hello React',
})
root.render(element);
不可否認的是使用 React.createElement
來建立 React DOM 真的很麻煩,而且也不太直覺,所以我們實際開發上還是以 JSX 為主。
就如同前面所言使用 React.createElement
來去撰寫 DOM 實在太困難,因此 React 才會出一個語法糖,也就是 JSX,而本身 JSX 就是 React.createElement
的語法糖。
因此舉例來講以下語法
const example = <h1>Hello React</h1>;
會被編譯成
const example = /*#__PURE__*/React.createElement("h1", null, "Hello React");
因此 JSX 本質上就是一個 createElement
語法糖。
如果是剛剛前面的 TodoList 巢狀範例(有刪除多餘程式碼,以便辨識)
const List = () => {
return (
<ul>
<li>這是列表</li>
</ul>
)
}
const App = () => {
return (
<div>
<h1>Hello React</h1>
<List/>
</div>
)
}
const root = ReactDOM.createRoot(document.querySelector('#root'));
root.render(<App />);
則會編譯成以下
const List = () => {
return /*#__PURE__*/(
React.createElement("ul", null, /*#__PURE__*/
React.createElement("li", null, "\u9019\u662F\u5217\u8868")));
};
const App = () => {
return /*#__PURE__*/(
React.createElement("div", null, /*#__PURE__*/
React.createElement("h1", null, "Hello React"), /*#__PURE__*/
React.createElement(List, null)));
};
const root = ReactDOM.createRoot(document.querySelector('#root'));
root.render( /*#__PURE__*/React.createElement(App, null));
透過前面範例我們可以知道一件事情,如果從頭到尾都用 createElement
撰寫的話,是真的會發瘋的。
舉例來講,下方有一個 HTML header
標籤,而不寫入任何內容
const App = () => <header className="text-center header"></header>
const root = ReactDOM.createRoot(document.querySelector("#root"));
root.render(<App />);
是可以使用自閉合的方式關閉標籤,達到更簡潔的狀況
const App = () => <header className="text-center header" />
const root = ReactDOM.createRoot(document.querySelector("#root"));
root.render(<App />);
而這兩者生出來的東西依然都是相同的
const App = () => /*#__PURE__*/React.createElement("header", { className: "text-center header" });
const root = ReactDOM.createRoot(document.querySelector("#root"));
root.render( /*#__PURE__*/React.createElement(App, null));
就算是元件也是一樣,以 TodoList 的章節就是這樣子撰寫
<List />
透過這方式,可以讓我們用更優雅簡潔的方式去撰寫 JSX。
JSX 中有一個很有趣的做法,你可以宣告一個物件,裡面放許多元件甚至也可以傳入 Props
const obj = {
MyName({ name }) {
return (<h1>Hello { name }</h1>)
},
List() {
return (
<ul>
<li>列表1</li>
<li>列表2</li>
</ul>
)
}
}
const App = () => {
return (
<div>
<h1>Hello React</h1>
<obj.myName name="Ray"/>
<obj.List />
</div>
)
}
const root = ReactDOM.createRoot(document.querySelector('#root'));
root.render(<App />);
前面章節你可以發現我們在建立一個 React 元件時,都是採用首字大寫的形式
const Cart = () => {
// ... 略
}
const List = () => {
// ... 略
}
const App = () => {
// ... 略
}
但我們並沒有提到原因是為什麼,其實原因是因為當你在撰寫 React 元件時,如果你的元件名稱首字是小寫的話,那麼 React 會認為你是在撰寫一個原生的 HTML 標籤,而不是一個自定義的元件。
什麼意思呢?這邊先來看一段範例
const App = () => {
return (
<div>
<header>Header</header>
<div>Hello React</div>
<footer>Footer</footer>
</div>
)
}
const root = ReactDOM.createRoot(document.querySelector('#root'));
root.render(<App />);
透過上方範例,我們可以看到編譯後的結果如下
const App = () => {
return /*#__PURE__*/(
React.createElement("div", null, /*#__PURE__*/
React.createElement("header", null, "Header"), /*#__PURE__*/
React.createElement("div", null, "Hello React"), /*#__PURE__*/
React.createElement("footer", null, "Footer")));
};
const root = ReactDOM.createRoot(document.querySelector('#root'));
root.render( /*#__PURE__*/React.createElement(App, null));
你可以看到 React.createElement
的第一個參數都是一個單純的字串。
如果將上方範例程式碼改成以下呢?
const header = () => <header>Header</header>;
const div = () => <div>Hello React</div>;
const footer = () => <footer>footer</footer>;
const App = () => {
return (
<div>
<header />
<div />
<footer />
</div>
)
}
const root = ReactDOM.createRoot(document.querySelector('#root'));
root.render(<App />);
這時候會發現一件有趣的事情,你會發現畫面完全不會出現任何東西,這是因為 React 認為你是在撰寫一個原生的 HTML 標籤,而不是撰寫元件,所以實際上 React 編譯出來的程式碼是這樣的
const header = () => /*#__PURE__*/React.createElement("header", null, "Header");
const div = () => /*#__PURE__*/React.createElement("div", null, "Hello React");
const footer = () => /*#__PURE__*/React.createElement("footer", null, "footer");
const App = () => {
return /*#__PURE__*/(
React.createElement("div", null, /*#__PURE__*/
React.createElement("header", null), /*#__PURE__*/
React.createElement("div", null), /*#__PURE__*/
React.createElement("footer", null)));
};
const root = ReactDOM.createRoot(document.querySelector('#root'));
root.render( /*#__PURE__*/React.createElement(App, null));
你會發現 React.createElement
的第一個參數都是一個單純的字串,而不是元件名稱。
接著讓我們看一下首字大寫的元件又會是如何
const Header = () => <header>Header</header>;
const Div = () => <div>Hello React</div>;
const Footer = () => <footer>footer</footer>;
const App = () => {
return (
<div>
<Header />
<Div />
<Footer />
</div>
)
}
const root = ReactDOM.createRoot(document.querySelector('#root'));
root.render(<App />);
基本上你會看到畫面正常出現了,接著我們看一下編譯後的程式碼
console.clear();
const Header = () => /*#__PURE__*/React.createElement("header", null, "Header");
const Div = () => /*#__PURE__*/React.createElement("div", null, "Hello React");
const Footer = () => /*#__PURE__*/React.createElement("footer", null, "footer");
const App = () => {
return /*#__PURE__*/(
React.createElement("div", null, /*#__PURE__*/
React.createElement(Header, null), /*#__PURE__*/
React.createElement(Div, null), /*#__PURE__*/
React.createElement(Footer, null)));
};
const root = ReactDOM.createRoot(document.querySelector('#root'));
root.render( /*#__PURE__*/React.createElement(App, null));
我們可以看到 React.createElement
的第一個參數不再是單純的字串,而是傳入一個變數,這也就是為什麼我們在撰寫元件時,首字母必須大寫的原因。
前面我們有介紹到 Props 的概念,但是在 JSX 中,如果你沒有給予 Props 值,那麼預設值就會是 true
const List = ({ data }) => {
return (
<ul>
<li>{ JSON.stringify(data) }</li>
</ul>
)
}
const App = () => {
return (
<div>
<List data />
</div>
)
}
const root = ReactDOM.createRoot(document.querySelector('#root'));
root.render(<App />);
而這個結果與你主動傳入 true
是一樣的
<List myName={ true }/>
只是以官方建議來講,還是會建議你要傳遞 Props 的資料,主要是避免與物件縮寫的觀念混淆,因為原本的物件寫法中,假設物件的 key 與 value 若相同,如:{ myName: myName }
是可以改寫成這樣的 { myName }
,而這是一個單純的物件縮寫。
JSX 預設狀況下是會清除開頭與結尾的空格,就算是換行符號也是一樣這一點要多加注意
const App = () => {
return (
<div>
<div>Hello React</div>
<div>
Hello React
</div>
<div>
Hello
React
</div>
<div>
Hello React
</div>
</div>
)
}
const root = ReactDOM.createRoot(document.querySelector('#root'));
root.render(<App />);
上面範例我們可以發現,不管怎麼用都是保持著一行文字的方式呈現。
JSX 另一個有趣的地方在於 Boolean
、Null
與 Undefined
是無法直接出現在畫面上的
const App = () => {
return (
<div>
<div>Boolean:{ true }</div>
<div>Boolean:{ false }</div>
<div>Null:{ null }</div>
<div>Undefined:{ undefined }</div>
</div>
)
}
const root = ReactDOM.createRoot(document.querySelector('#root'));
root.render(<App />);
但這並不代表 JSX 會發生錯誤,而是單純不會被 render
而已。
如果想要被輸出的話,可以使用 String
來做型別轉換,只要轉換成一個字串就可以正常輸出了
const App = () => {
return (
<div>
<div>Boolean:{ String(true) }</div>
<div>Boolean:{ String(false) }</div>
<div>Null:{ String(null) }</div>
<div>Undefined:{ String(undefined) }</div>
</div>
)
}
const root = ReactDOM.createRoot(document.querySelector('#root'));
root.render(<App />);
當然其實不只有使用 String
這種方式,以下我也列出了各種解決方式
const App = () => {
return (
<div>
<div>Boolean:{ String(true) }</div>
<div>Boolean:{ true.toString() }</div>
<div>Boolean:{ true + '' }</div>
<div>Boolean:{ `${true}` }</div>
<div>Boolean:{ JSON.stringify(true) }</div>
</div>
)
}
const root = ReactDOM.createRoot(document.querySelector('#root'));
root.render(<App />);
本文將會同步更新到我的部落格