跳至主要内容

環境架構

10/1/2022 發布

React 的環境可以直接使用 Vite 建構

老 React 仔一定都玩過的 CRA(create-react-app)已經停止維護了,RIP。

注意

官網在 installation 這個頁面安排的第一個章節是 Creating a React App
裡面都是現成套件的 template,如 create-next-app@latest 會建出一個 Next.js 的專案...
create-react-router@latest會強制安裝好 TypeScript 和 Tailwind,
可能不是很適合新手練習。

SPA

原本做網頁時,只要右鍵新增資料夾,再新增 html, css, js 檔後就大致建好環境了。

React 的專案資料夾裡會有一個 index.html 和一些設定檔,
這是因為前端框架大多是 SPA,頁面可以在不重新整理的狀況下導航到另一個頁面的內容,
所以網址看起來有改變,但實際上程式碼還在是在同一個 html 檔上運作,
背後的原理也是操作 DOM 改變index.html,但不是跳轉到另一支 html 檔。


createRoot

index.html 裡面只有一個 id 為 root 的 div,這是程式碼的進入點
React 將會抓取這個元素並在裡面加入其他元素,類似 innerHTML 的邏輯:

const root = ReactDOM.createRoot(document.querySelector('#root'));
root.render('要產生的東西');

選取到 id="root" 的元素後,呼叫 createRoot 並傳入剛剛選取到的 div,
回傳結果會存到變數 root

呼叫 root.render 時傳入的參數內容就會顯示在 <div id="root"></div> 裡面。


JSX

createRoot 通常只會在網頁開啟的時候執行一次,
剛剛示範 render 的參數是傳入字串 '要產生的東西'
一般會傳入React Element

它是 React 系統裡的物件,最後會被轉換成 DOM 顯示在畫面上:

const sectionElement = React.createElement(
'section', // HTML 標籤名稱
{
id: 'sectionId', // 標籤屬性
className: 'section'
},
childrenElement, // 子元素,如果有多個就會一直逗號串下去
);

root.render(sectionElement);

但這種定義元素的方式不太直覺,更不用說要編寫出一整頁的內容結構,
所以 JSX 就被發明出來了,看起來很像 HTML,
實際上是把這串像 HTML 的東西轉換成上面 createElement 的過程:

const jsxElement = <h1>這就是 JSX!</h1>;

root.render(jsxElement);

看起來比寫一堆物件,還要同時聯想它實際上顯示的樣子友善多了吧!

瀏覽器只能解析 JavaScript,所以需要透過 Babel 這類的工具轉換 JSX 語法,
Vite 等建構工具都已經處理好轉換的設定了。


component

除了 HTML 標籤,JSX 也允許客製標籤,被稱作元件(component)。
元件是透過函式宣告產生的,函式名稱的開頭必須是大寫,才會被 React 辨識成元件:

function Component() {
return <span>這就是元件!</span>;
}

元件可以自行組合 HTML 的結構再回傳出來,
但要注意這些元素都必須包在一個標籤裡面

回想剛剛 createRootcreateElement 的過程,
會發現產生 section 元素時,我們是把子元素串在children 這個 key 上的,
也就是 createElement 創造出來的元件會被轉換成一個元素並且包住多個子元素:

<section>
<!-- 參數裡面帶到的 children 會被塞在這 -->
<section>

所以如剛剛宣告 Component 時,回傳的結構是一組 span 標籤,
是可以解析的,但回傳 2 組同層的標籤就會報錯。

不需要 div 的話用空標籤或是 <Fragment> 也是合法的,
它們不會成為 DOM 節點,也不會出現在畫面上:

function Component() {
return (
<>
<span>這就是元件!</span>
<span>這就是元件!</span>
</>
);
}

function FragmentComponent() {
return (
<Fragment>
<span>這就是元件!</span>
<span>這就是元件!</span>
</Fragment>
);
}

定義完元件之後,放到 root.render 時就可以寫成標籤,
元件內容就可以在畫面上看到:

const root = ReactDOM.createRoot(document.querySelector('#root'));
root.render(<Component />);

props

元件是透過 function 宣告建立的,所以能定義參數,
被 React 解析成元件的函式都會自帶一個參數 props

JSX 和 HTML 標籤一樣是都是標籤結構,可以包住文字、其他標籤或元件,
被包住的東西可以在 props 裡面用 children 取出來:

function Component(props) {
return <div>{ props.children }</div>; // <div>我是被包住的文字</div>
}

const root = ReactDOM.createRoot(document.querySelector('#root'));
root.render(<Component>我是被包住的文字</Component>);

如上面範例,在 Component 標籤裡面的'我是被包住的文字'
可以透過 props.children 取出,並渲染在 div 裡面。

自定義的屬性也能傳入任何東西,到內層再透過 props 取出來,
就像是 HTML 標籤的屬性,用 屬性名稱={變數} 的格式就能傳入變數:

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 = ReactDOM.createRoot(document.querySelector('#root'));

root.render(
<Component
text="屬性 text 的文字"
obj={obj}
element={element}
>
我是被包住的文字
</Component>
);

Component 標籤寫上 textobjelement 屬性後,
Component 的定義裡面可以自行決定怎麼使用這些屬性。


coding style

雖然範例都很簡短,不過 React 因為發展得久,形成了一些江湖慣例要遵守,
像是 onClick 的內容最好可以拉出來宣告,才不致於讓 JSX 變得很冗長,
與互動事件有關的函式,命名也會以 handle 開頭,表示要處理特定的 UI 互動事件,
例如 handleClick, handleChange, handleFocus 等:

function App() {
// 函式先在 JSX 之前定義好,而不直接寫在 JSX 裡面
function handleClickAddBtn() {
// 要做的事...
}

return (
<button
type="button"
onClick={handleClickAddBtn}
>
加 1
</button>
);
}

有傳入屬性時也可以考慮把 props 解構:

function Component({ name, id, time }) {
//...
}

參考資料