iT邦幫忙

2022 iThome 鐵人賽

DAY 12
0
Modern Web

終究都要學 React 何不現在學呢?系列 第 12

終究都要學 React 何不現在學呢? - React 進階 - 深入 JSX - (12)

  • 分享至 

  • xImage
  •  

前言

前面章節雖然我們已經認識了基本的 JSX 的部分,但這一篇我們會更深入的認識了解 JSX,讓我們在開發的時候可以少一點雷點,甚至活用一些技巧。

createElement

還記得我們最早前面是怎麼建立 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);

CodePen 連結

不可否認的是使用 React.createElement 來建立 React DOM 真的很麻煩,而且也不太直覺,所以我們實際開發上還是以 JSX 為主。

JSX 就是語法糖

就如同前面所言使用 React.createElement 來去撰寫 DOM 實在太困難,因此 React 才會出一個語法糖,也就是 JSX,而本身 JSX 就是 React.createElement 的語法糖。

因此舉例來講以下語法

const example = <h1>Hello React</h1>;

會被編譯成

const example = /*#__PURE__*/React.createElement("h1", null, "Hello React");

CodePen 連結

因此 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));

CodePen 連結

透過前面範例我們可以知道一件事情,如果從頭到尾都用 createElement 撰寫的話,是真的會發瘋的。

自閉合 (self-closing)

舉例來講,下方有一個 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));

CodePen 連結

就算是元件也是一樣,以 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 />);

CodePen 連結

自定義的 Components 首字必須為大寫

前面章節你可以發現我們在建立一個 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 />);

CodePen 連結

透過上方範例,我們可以看到編譯後的結果如下

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 />);

CodePen 連結

這時候會發現一件有趣的事情,你會發現畫面完全不會出現任何東西,這是因為 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 />);

CodePen 連結

基本上你會看到畫面正常出現了,接著我們看一下編譯後的程式碼

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 預設為 True

前面我們有介紹到 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 />);

CodePen 連結

而這個結果與你主動傳入 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 />);

CodePen 連結

上面範例我們可以發現,不管怎麼用都是保持著一行文字的方式呈現。

特定型別會被忽略

JSX 另一個有趣的地方在於 BooleanNullUndefined 是無法直接出現在畫面上的

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 />);

CodePen 連結

但這並不代表 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 />);

CodePen 連結

當然其實不只有使用 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 />);

CodePen 連結

後記

本文將會同步更新到我的部落格


上一篇
終究都要學 React 何不現在學呢? - React 基礎 - TodoList (下) - (11)
下一篇
終究都要學 React 何不現在學呢? - React 進階 - useMemo - (13)
系列文
終究都要學 React 何不現在學呢?30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

1
一顆蘋果熊
iT邦新手 5 級 ‧ 2022-09-26 14:18:49

哇!!!!!!我好像懂了什麼!!

Ray iT邦研究生 4 級 ‧ 2022-09-26 14:22:01 檢舉

哇!!!!!你竟然學會了!!

我要留言

立即登入留言