環境架構

建構

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.htmlbody 只有一個 <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 }) {
//...
}

參考資料