# React 是什麼? React 是由 Facebook 在 2013 年開源的 **用戶介面(UI)函式庫**,專注在 MVC/MVVM 架構中的 **View(V)**。 * **輕量聚焦**:只負責畫面層,不包山包海,其他需求(狀態管理、路由、資料抓取)多仰賴生態系套件搭配。這也讓它 **可組合、可替換**,靈活度高。 * **設計哲學**:*Learn Once, Write Anywhere*──學會一次概念,可在 Web、行動(React Native)等多種平台應用。 * **React Native**([https://reactnative.dev/](https://reactnative.dev/)):以 React 思維開發近原生的 iOS / Android App;樣式語法類似 CSS;底層以橋接方式渲染到原生元件。 > 小提醒:React 並不是 MVVM 的 ViewModel,它更像是「把 UI 拆成一塊塊元件,**以資料驅動畫面**」的思維工具。 <br/> ## 核心特點 1. **宣告式 UI(Declarative)** 你描述「狀態如何 → 畫面就會長怎樣」。這讓程式可讀性高,行為可預測,也更容易除錯與測試。 2. **組件化(Componentization)** 把 UI 拆成可重用的小元件(Button、Card、ListItem…),**各自單純、好維護**。元件可組合成更大的區塊,像堆積木一樣蓋出整個應用。 3. **單向資料流(One-way Data Flow)** 資料由父元件透過 `props` 往子元件流動;子元件不能直接改 `props`,而是透過回呼把意圖(事件)往上拋,讓父元件決定如何更新狀態。 4. **虛擬 DOM(Virtual DOM)** React 先在記憶體中建一棵虛擬樹(Virtual DOM),狀態變更時用**差異比對**(diffing)計算「最小必要更新」,再一次套到真實 DOM,通常能帶來良好表現與更簡潔的心智模型。 ![image](https://hackmd.io/_uploads/HyZwTSbYC.png) <br/> ## 基本概念 * **JSX**:JavaScript 的語法擴充,讓你可以在 JS 內直接寫近似 HTML 的標記,來描述 UI 結構。 * **元件(Component)**:可用 Class 或 Function 寫成。接受輸入 `props`,回傳要渲染的 React 元素。 * **狀態(State)與屬性(Props)**: * `state`:元件內部、可變的資料(會驅動畫面重新渲染)。 * `props`:外部傳入、不可在子元件內直接修改的資料。 * **生命週期 / Hooks**:Class 元件有對應的生命週期方法;函式元件透過 **Hooks**(如 `useEffect`)達成掛載、更新、卸載時機的副作用處理。 <br/> ## 為什麼需要 JSX? **JSX(JavaScript XML)** 不是必須,但非常好用: * **直觀**:把 UI 結構以「看得見的樹」寫出來。 * **高效率**:標記與邏輯同檔共流,讀寫切換快。 * **工具友善**:型別提示、語法高亮、Lint 與錯誤早期發現。 * **與虛擬 DOM 契合**:JSX 會被轉譯成 `React.createElement(...)` 呼叫,產生虛擬 DOM。 > 注意:瀏覽器不懂 JSX,需用 **Babel** 等工具轉譯成標準 JavaScript 才能執行。 --- ## 範例:不用 JSX(原生 API) vs. 使用 JSX **不使用 JSX(等價寫法)** ```jsx class HelloMessage extends React.Component { render() { return React.createElement('div', null, 'Hello ', this.props.name); } } ReactDOM.render( React.createElement(HelloMessage, { name: 'Taylor' }), document.getElementById('hello-example') ); ``` **使用 JSX** ```jsx class HelloMessage extends React.Component { render() { return <div>Hello {this.props.name}</div>; } } ReactDOM.render( <HelloMessage name="Taylor" />, // 單向資料流的傳入 document.getElementById('hello-example') ); ``` <br/> ## 不經過編譯,直接在瀏覽器使用 React 在學習或做小玩具時,你可以**不設定打包工具**,直接以 `<script>` 引入 UMD 版本: ```html <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>React Without Build Tools</title> </head> <body> <div id="root"></div> <script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script> <script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script> <script> 'use strict'; class HelloMessage extends React.Component { render() { return React.createElement('div', null, `Hello ${this.props.name}`); } } const root = document.querySelector('#root'); ReactDOM.render(React.createElement(HelloMessage, { name: 'React' }), root); </script> </body> </html> ``` 若想在瀏覽器**直接寫 JSX**,可再引入 **Babel Standalone**,並把腳本的 `type` 設為 `text/babel`: ```html <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> <script type="text/babel"> 'use strict'; class HelloMessage extends React.Component { render() { return <div>Hello {this.props.name}</div>; } } const root = document.querySelector('#root'); ReactDOM.render(<HelloMessage name="React" />, root); </script> ``` > **實務建議**:UMD / Babel Standalone 很適合入門或簡報 Demo,但**不建議用於生產**(沒有模組化、HMR、Tree Shaking…)。開發正式專案請使用 Vite / Next.js / CRA(舊)等工具鏈。 <br/> ## 函式元件(Function Components)與 Hooks **函式元件** 是現代 React 的主流。搭配 **Hooks**,幾乎可取代 Class 元件的所有場景。 ### `useState`:管理狀態 ```html <script type="text/babel"> const { useState } = React; function Counter() { const [count, setCount] = useState(0); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}>Click me</button> </div> ); } const root = document.querySelector('#root'); ReactDOM.render(<Counter />, root); </script> ``` 也可以管理**陣列或物件**狀態: ```html <script type="text/babel"> const { useState } = React; function Products() { const [products, setProducts] = useState([ { name: 'pen', price: 22 }, { name: 'apple', price: 44 }, ]); const addProduct = () => setProducts([...products, { name: 'book', price: 55 }]); return ( <div> <button type="button" onClick={addProduct}>Add</button> {products.map(({ name }) => ( <div key={name}>{name}</div> ))} </div> ); } const root = document.querySelector('#root'); ReactDOM.render(<Products />, root); </script> ``` ### `useEffect`:處理副作用 ```html <script type="text/babel"> const { useState, useEffect } = React; function Counter() { const [count, setCount] = useState(0); useEffect(() => { document.title = `You clicked ${count} times`; }); // 省略相依陣列:每次 render 後都會執行 // 只在掛載一次時執行: // useEffect(() => { initSomething(); }, []); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}>Click me</button> </div> ); } const root = document.querySelector('#root'); ReactDOM.render(<Counter />, root); </script> ``` <br/> ## 簡易「路由」小練習(不靠套件) 用一個 `page` 狀態來控制要顯示的頁面內容: ```html <script type="text/babel"> 'use strict'; const { useState } = React; const App = () => { const [page, setPage] = useState('1'); return ( <main> <Navbar page={page} setPage={setPage} /> <Header page={page} /> </main> ); }; const Navbar = ({ page, setPage }) => { const routes = [ { name: 'link1', link: '1' }, { name: 'link2', link: '2' }, { name: 'link3', link: '3' }, { name: 'link4', link: '4' }, ]; return ( <nav className="test"> <ul> {routes.map(({ name, link }) => ( <li key={name}> <a href="#" onClick={(e) => { e.preventDefault(); setPage(link); }}>{name}</a> </li> ))} </ul> </nav> ); }; const Header = ({ page }) => <h1>{page}</h1>; ReactDOM.render(<App />, document.getElementById('root')); </script> ``` --- ## 常見誤解 & 小提醒 * **JSX 不是 HTML**:屬性命名與 HTML 不完全相同(如 `className`、`htmlFor`)。 * **不要直接改 state**:用 Setter(如 `setCount(n + 1)`),或以函式型寫法安全地取舊值。 * **Effect 相依陣列**:缺少依賴會造成隱性 bug;ESLint 規則能幫你補齊。 * **UMD 僅適合學習/試玩**:正式專案請用現代工具鏈(Vite / Next.js)。