環境架構
建構
React 專案可以用 Vite 生成。
早期 React 專案大多會用 CRA (
create-react-app,使用 Webpack 建構),但 CRA 已經停止維護,RIP。
官網在 installation 會誘導你使用 create-next-app@latest 指定建立專案,但實際上會建出一個 Next.js 專案。
而 create-react-router@latest 會強制安裝好 TypeScript 和 Tailwind,對不熟悉這些技術的初學者來說可能不太友善。
SPA
基本的網頁只要右鍵新增資料夾,再新增 .html, .css, .js 檔後就大致建好專案了。
React 專案生成時只有一個 index.html 和一些設定檔,這是因為前端框架的呈現大多是 SPA (Single-Page Application),網頁可以在不重新導向的前提下呈現另一個頁面,所以網址看起來有改變,但實際上是程式碼改變了同一個 .html 檔的內容。
背後的原理也是操作 DOM 改變 index.html,但不是跳轉到另一個 .html 檔。
所以 SPA 不會像傳統網站一樣有多個預先寫好 HTML 結構的 .html 檔,而是全部交由程式碼產生 HTML 結構。
createRoot
index.html 的 body 只有一個 <div id="root">,main.js 的程式碼會抓取這個元素並在裡面顯示真正的網站內容:
const root = createRoot(document.querySelector('#root'));root.render('要產生的東西');root.render 的參數會變成 DOM 元素顯示在 <div id="root"> 裡面。
createRoot 是剛進到網頁時的啟動程序,通常只會執行一次,後續的換頁只需要透過程式碼處理。
JSX
剛剛示範 render 的參數是傳入字串 '要產生的東西',一般會傳入React Element。
它是 React 系統裡的物件,最後會被轉換成 DOM 元素:
const sectionElement = createElement( 'section', // HTML 標籤名稱 { id: 'sectionId', // 標籤屬性 className: 'section', }, childrenElement, // 內層元素,如果有多個並列的元素就會一直逗號串下去);
root.render(sectionElement);但這種方式要編排頁面內容是不太直覺,所以 JSX 就誕生了,看起來很像 HTML,實際上是把這串像 HTML 的東西轉換成上面 createElement 的過程:
const jsxElement = <h1>這就是 JSX!</h1>;
root.render(jsxElement);看起來比定義一堆物件,還要同時聯想它實際上顯示的樣子友善吧!
但瀏覽器只能解析原生的 JavaScript,所以 JSX 語法在以前是需要在 Webpack 設定中整合 Babel 這個外掛,而 Vite 已經處理好基礎設定,我們只要安心寫下去就好。
component
JSX 也允許客製標籤,被稱作元件 (component)。
元件是透過函式宣告產生的,函式名稱的開頭必須是大寫,才會被 React 辨識成元件變數:
function Component() { return <span>這就是元件!</span>;}以標籤語法 <函式名稱> 呼叫就可以使用元件:
function App() { return <Component />;}直接呼叫函式雖然也可以生成 Component 中的 HTML 結構,但不會被視為元件,因此不能整合任何 React 的 hook:
function App() { return <>{Component()}</>;}元件可以任意組織 HTML 結構,但必須把這些元素包在一個標籤裡面!
回想剛剛 createElement 的過程,定義 <section> 時,預期要包在裡面的元素會放在 createElement的最後一個參數 。
不論 createElement 怎麼層層遞進,本身就代表樹狀結構的節點,最後的格式當然就要收束成一個標籤:
<section> <!-- 參數 --></section>所以如剛剛宣告 Component 時,回傳的結構是一組 <span>,符合格式,但回傳 2 組並列同層的標籤就會報錯:
// 會報錯function Component() { return ( <span>這就是元件!</span> <span>這就是元件!</span> );}想讓實際的 DOM 結構比較乾淨的話,空標籤或是 <Fragment> 也是合法格式,它們不會成為真實的 DOM:
function Component() { return ( <> <span>這就是元件!</span> <span>這就是元件!</span> </> );}
function FragmentComponent() { return ( <Fragment> <span>這就是元件!</span> <span>這就是元件!</span> </Fragment> );}props
元件是透過 function 宣告建立的,所以能定義參數,被 React 解析成元件的函式都會自帶一個固定參數 props。
JSX 和 HTML 標籤一樣是都是標籤結構,可以包住文字、其他標籤或元件,被包住的東西可以在 props 裡面用 children 取出來:
function Component(props) { return <div>{props.children}</div>; // <div>我是被包住的文字</div>}
const root = createRoot(document.querySelector('#root'));root.render(<Component>我是被包住的文字</Component>);除了 children,也可以自定義其他屬性並在 JSX 標籤中傳入,透過 props 取出來,就像是在使用 HTML 標籤的 getAttribute(attributeName):
function Component(props) { return ( <div> {/* 我是被包住的文字 */} {props.children}
{/* obj 的 name */} {props.obj.name}
{/* <div>我是 div</div> */} {props.element}
{/* 屬性 text 的文字 */} {props.text} </div> );}
const obj = { name: 'obj 的 name' };const element = <div>我是 div</div>;const root = createRoot(document.querySelector('#root'));
root.render( <Component text="屬性 text 的文字" obj={obj} element={element}> 我是被包住的文字 </Component>,);在 Component 的定義裡面可以自行決定怎麼使用外面傳入的這些屬性。
coding style
React 因為發展得久,形成了一些江湖慣例要遵守,像是 onClick 的內容最好可以拉出來宣告,才不致於讓 JSX 變得很冗長。
與互動事件有關的函式,命名也會以 handle 開頭,表示要處理特定的 UI 互動事件,例如 handleClick, handleChange, handleFocus 等:
function App() { // 函式先在 JSX 之前定義好,而不直接寫在 JSX 裡面 function handleAddClick() { // 要做的事... }
return ( <button type="button" onClick={handleAddClick}> 加 1 </button> );}有傳入屬性時也可以考慮把 props 解構:
function Component({ name, id, time, ...props }) { //...}