---
# System prepended metadata

title: React 從零到一：兩週學習計畫

---

# React 從零到一：兩週學習計畫

歡迎來到 React 的世界！本計畫專為初學者設計，旨在透過為期兩週、每天約兩小時的學習，幫助你掌握 React 開發的核心技能。

**學習路徑：**
*   **第一週：React 核心基礎** - 我們將專注於 React 的基本概念，包括 JSX、元件、Props、State 以及事件處理。
*   **第二週：進階概念與實戰** - 我們將學習 Hooks 的進階用法、路由、全域狀態管理，並最終完成一個小型實踐專案。

---

## 第一週：React 核心基礎

### Day 1: React 簡介與環境設定

**今日目標：** 了解 React 是什麼，以及如何設定你的第一個 React 開發環境。

#### 1. 什麼是 React？
React 是一個由 Facebook 開發的開源 JavaScript UI 函式庫。它的主要特點是：
*   **元件化 (Component-Based):** 將複雜的 UI 拆分成一個個獨立、可重複使用的元件，讓程式碼更容易管理和維護。
*   **宣告式 (Declarative):** 你只需要告訴 React 你希望 UI 長什麼樣子，React 會自動處理畫面的更新。
*   **虛擬 DOM (Virtual DOM):** React 在記憶體中維護一個輕量級的 DOM 副本。當狀態改變時，它會先計算出新舊虛擬 DOM 的差異，然後才以最高效率的方式更新真實的 DOM，從而提升應用程式的效能。

#### 2. 環境設定
開始之前，你需要安裝 Node.js 和 npm（Node.js 的套件管理器）。
1.  前往 [Node.js 官網](https://nodejs.org/) 下載並安裝 LTS (長期支援) 版本。
2.  安裝完成後，打開你的終端機（Terminal 或 Command Prompt），輸入以下指令來確認是否安裝成功：
    ```bash
    node -v
    npm -v
    ```
    如果能看到版本號，代表安裝成功。

#### 3. 建立你的第一個 React 專案
我們使用官方推薦的工具 `Create React App` 來快速建立專案。

在你的終端機中，進入你想要建立專案的資料夾，然後執行：
```bash
npx create-react-app my-first-react-app
```
這個指令會建立一個名為 `my-first-react-app` 的資料夾，並在其中設定好所有必要的開發工具。

建立完成後，進入專案目錄並啟動開發伺服器：
```bash
cd my-first-react-app
npm start
```
你的瀏覽器會自動打開 `http://localhost:3000`，並顯示 React 的歡迎頁面。

#### **練習 1**
*   **題目：** 成功建立一個新的 React 專案，並修改 `src/App.js` 檔案，將畫面中的預設內容替換成 "Hello, React!"。
*   **提示：** 找到 `src/App.js` 中的 `<header>` 標籤，並將其內容替換掉。

#### **答案 1**
打開 `src/App.js`，將其內容修改如下：

```javascript
import './App.css';

function App() {
  return (
    <div className="App">
      <h1>Hello, React!</h1>
    </div>
  );
}

export default App;
```
儲存檔案後，瀏覽器中的畫面會自動更新。

> #### **語法小教室：**
> *   `import './App.css';`：這是在引入 CSS 檔案，用來美化你的元件。`./` 表示檔案位於同一個資料夾。
> *   `function App() { ... }`：這是在定義一個 JavaScript **函式 (Function)**，在 React 中，這種函式被稱為**函式元件**。`App` 是這個元件的名字。
> *   `return ( ... );`：函式元件必須回傳一些東西，這些東西就是你想顯示在畫面上的內容。括號 `()` 是為了可以寫多行內容。
> *   `<div>`, `<h1>`：這些看起來像 HTML 的標籤，其實是 **JSX**，我們明天會學到。`className` 對應到 HTML 的 `class` 屬性。
> *   `export default App;`：這是在「匯出」這個 `App` 元件，這樣其他檔案（例如 `index.js`）才能「引入」並使用它。`default` 表示這是這個檔案最主要的匯出項目。

---

### Day 2: JSX 語法與元件 (Components)

**��日目標：** 學習 JSX 語法，並理解 React 中最重要的概念——元件。

#### 1. 什麼是 JSX？
JSX (JavaScript XML) 是一個 JavaScript 的語法擴充。它讓你在 JavaScript 程式碼中可以像寫 HTML 一樣來描述 UI 結構。

**主要特點：**
*   可以在 `{}` 中嵌入任何 JavaScript 表達式。
*   HTML 屬性 `class` 在 JSX 中要寫成 `className`。
*   所有標籤都必須閉合，例如 `<img />` 或 `<div></div>`。
*   一個元件回傳的 JSX 必須被單一一個父層元素所包裹。

**範例：**
```javascript
const name = "Alice";
const element = <h1>Hello, {name}</h1>; // 在 JSX 中使用變數

const user = {
  firstName: 'Bob',
  lastName: 'Smith'
};

function formatName(user) {
  return user.firstName + ' ' + user.lastName;
}

const element2 = <h2>Welcome back, {formatName(user)}!</h2>; // 在 JSX 中使用函式
```
> #### **語法小教室：**
> *   `const name = "Alice";`：`const` 是用來宣告一個**常數 (Constant)**，代表這個 `name` 變數在宣告後就不能再被改變。
> *   `{ ... }`：在 JSX 中，大括號 `{}` 是一個「魔法通道」，可以讓你把 JavaScript 的變數、函式執行結果，甚至是簡單的計算（例如 `{1 + 1}`）放��去，React 會將其結果顯示在畫面上。

#### 2. 函式元件 (Functional Components)
在現代 React 中，我們主要使用函式元件。它就是一個回傳 JSX 的 JavaScript 函式。

**範例：**
在 `src` 資料夾下建立一個新檔案 `Greeting.js`：
```javascript
// src/Greeting.js
function Greeting() {
  return <h2>This is a greeting component.</h2>;
}

export default Greeting;
```
然後在 `App.js` 中引入並使用它：
```javascript
// src/App.js
import './App.css';
import Greeting from './Greeting'; // 引入 Greeting 元件

function App() {
  return (
    <div className="App">
      <h1>Hello, React!</h1>
      <Greeting /> {/* 使用 Greeting 元件 */}
    </div>
  );
}

export default App;
```
> #### **語法小教室：**
> *   `import Greeting from './Greeting';`：從 `./Greeting.js` 這個檔案引入它所匯出的 `Greeting` 元件。
> *   `<Greeting />`：這就是使用一個 React 元件的方式，看起來就像一個自訂的 HTML 標籤。因為它沒有任何子內容，所以可以用 `/>` 來自我閉合。

#### **練習 2**
*   **題目：** 建立一個名為 `UserProfile.js` 的新元件。這個元件要顯示一個使用者的基本資料，包括姓名、���齡和一句自我介紹。在 `App.js` 中使用這個元件。
*   **提示：** 在 `UserProfile.js` 中定義一些變數來儲存使用者資料，並在 JSX 中用 `{}` 來顯示它們。

#### **答案 2**
建立 `src/UserProfile.js`：
```javascript
// src/UserProfile.js
function UserProfile() {
  const user = {
    name: 'Charlie',
    age: 30,
    bio: 'I am a software developer and a React enthusiast.'
  };

  return (
    <div>
      <h3>User Profile</h3>
      <p><strong>Name:</strong> {user.name}</p>
      <p><strong>Age:</strong> {user.age}</p>
      <p><strong>Bio:</strong> {user.bio}</p>
    </div>
  );
}

export default UserProfile;
```
修改 `src/App.js` 來使用它：
```javascript
// src/App.js
import './App.css';
import UserProfile from './UserProfile';

function App() {
  return (
    <div className="App">
      <h1>My Application</h1>
      <UserProfile />
    </div>
  );
}

export default App;
```

---

### Day 3: Props - 元件的屬性

**今日目標：** 學習如何使用 `props` 將資料從父元件傳遞到子元件。

#### 1. 什麼是 Props？
`Props` (properties 的縮寫) 是元件的屬性。它是一個物件，允許你將資料從父元件傳遞到子元件，讓元件變得可重複使用且更具動態性。Props 是唯讀的，子元件不應該直接修改接收到的 props。

#### 2. 如何傳遞與接收 Props
在父元件中，你可以像 HTML 屬性一樣將資料傳遞給子元件。在子元件中，可以透過函式的第一個參數來接收 `props` 物件。

**範例：**
修改 `Greeting.js`，讓它可以接收一個 `name` 的 prop。
```javascript
// src/Greeting.js
function Greeting(props) {
  // props 是一個物件: { name: "value_from_parent" }
  return <h2>Hello, {props.name}!</h2>;
}

export default Greeting;
```
> #### **語法小教室：**
> *   `function Greeting(props) { ... }`：`props` 是函式元件的**參數 (Parameter)**。當父元件傳遞屬性給 `Greeting` 元件時，React 會將這些屬性打包成一個物件，並作為 `props` 參數傳入。
> *   `{props.name}`：因為 `props` 是一個物件，例如 `{ name: "Alice" }`，所以我們可以用 `.` 來存取物件裡面的屬性值。

在 `App.js` 中傳遞 `name` prop 給 `Greeting` 元件。
```javascript
// src/App.js
import './App.css';
import Greeting from './Greeting';

function App() {
  return (
    <div className="App">
      <Greeting name="Alice" />
      <Greeting name="Bob" />
      <Greeting name="Charlie" />
    </div>
  );
}

export default App;
```

#### **練習 3**
*   **題目：** 修改昨天的 `UserProfile` 元件，讓它接收 `user` 物件作為一個 prop，而不是在元件內部寫死。在 `App.js` 中建立兩個不同的使用者物件，並使用 `UserProfile` 元件兩次來顯示它們的資料。
*   **提示：** 在 `App.js` 中定義 `user1` 和 `user2` 物件，然後像這樣傳遞：`<UserProfile user={user1} />`。

#### **答案 3**
修改 `src/UserProfile.js`：
```javascript
// src/UserProfile.js
function UserProfile(props) {
  // 從 props 中解構出 user
  const { user } = props;

  return (
    <div style={{ border: '1px solid #ccc', margin: '10px', padding: '10px' }}>
      <h3>User Profile</h3>
      <p><strong>Name:</strong> {user.name}</p>
      <p><strong>Age:</strong> {user.age}</p>
      <p><strong>Bio:</strong> {user.bio}</p>
    </div>
  );
}

export default UserProfile;
```
> #### **語法小教室：**
> *   `const { user } = props;`：這是一種稱為「**物件解構 (Object Destructuring)**」的語法糖。它相當於 `const user = props.user;`。這讓我們可以更方便地從物件中取出想要的屬性並存成變數。
> *   `style={{...}}`：在 JSX 中設定行內樣式需要使用兩個大括號。外層的 `{}` 表示這是一個 JavaScript 表達式，內層的 `{}` 則是一個真正的 JavaScript 物件，用來定義 CSS 樣式。屬性名稱要用駝峰式寫法，例如 `border-color` 要寫成 `borderColor`。

修改 `src/App.js` 來使用它：
```javascript
// src/App.js
import './App.css';
import UserProfile from './UserProfile';

function App() {
  const user1 = {
    name: 'David',
    age: 25,
    bio: 'Loves hiking and photography.'
  };

  const user2 = {
    name: 'Eva',
    age: 32,
    bio: 'Enjoys reading and playing the piano.'
  };

  return (
    <div className="App">
      <h1>User Management</h1>
      <UserProfile user={user1} />
      <UserProfile user={user2} />
    </div>
  );
}

export default App;
```

---

### Day 4: State 與 useState Hook

**今日目標：** 學習元件的 `state` 以及如何使用 `useState` Hook 來管理元件的內部狀態。

#### 1. 什麼是 State？
如果說 `props` 是從外部傳入的資料，那麼 `state` 就是���件內部自己管理的資料。當 `state` 改變時，React 會自動重新渲染 (re-render) 該元件，以反映最新的狀態。

#### 2. `useState` Hook
`useState` 是 React 提供的一個 **Hook** (鉤子)，讓我們可以在函式元件中使用 state。你可以把 Hook 想像成一個能「鉤」住 React 核心功能（如 state）的特殊函式。

*   **引入：** `import { useState } from 'react';`
*   **使用：** `const [state, setState] = useState(initialState);`
    *   `useState` 接收一個 `initialState` (初始狀態) 作為參數。
    *   它回傳一個陣列，包含兩個元素：
        1.  `state`：目前的狀態值。
        2.  `setState`：一個用來更新此狀態的函式。

**範例：一個計數器**
建立一個新元件 `Counter.js`。
```javascript
// src/Counter.js
import { useState } from 'react';

function Counter() {
  // 宣告一個名為 count 的 state，初始值為 0
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      {/* 點擊按鈕時，呼叫 setCount 來更新 count 的值 */}
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

export default Counter;
```
> #### **語法小��室：**
> *   `import { useState } from 'react';`：從 `react` 套件中引入 `useState` 這個 Hook。這裡的 `{}` 表示我們是「具名引入」，只引入我們需要的 `useState` 部分。
> *   `const [count, setCount] = useState(0);`：這是一種「**陣列解構 (Array Destructuring)**」語法。`useState(0)` 會回傳一個陣列，例如 `[0, function]`。我們把陣列的第一個元素賦值給 `count` 變數，第二個元素賦值給 `setCount` 變數。
> *   `setCount(count + 1)`：這是在**呼叫 (Call)** `setCount` 函式，並傳入新的狀態值 `count + 1`。當這個函式被呼叫後，React 就會安排一次元件的重新渲染。

在 `App.js` 中使用它：
```javascript
// src/App.js
import './App.css';
import Counter from './Counter';

function App() {
  return (
    <div className="App">
      <h1>Counter Example</h1>
      <Counter />
    </div>
  );
}

export default App;
```

#### **練習 4**
*   **題目：** 建立一個 `LightSwitch.js` 元件。這個元件有一個按鈕，按鈕上顯示 "Turn On" 或 "Turn Off"。畫面上同時顯示 "The light is On" 或 "The light is Off"。點擊按鈕可以切換燈的開關狀態。
*   **提示：** 使用 `useState` 來儲存一個布林值（`true` 或 `false`）來代表燈的狀態。

#### **答案 4**
建立 `src/LightSwitch.js`：
```javascript
// src/LightSwitch.js
import { useState } from 'react';

function LightSwitch() {
  // true 代表開，false 代表關
  const [isOn, setIsOn] = useState(false);

  const toggleLight = () => {
    // 使用 setIsOn 來更新狀態，值為目前狀態的相反
    setIsOn(!isOn);
  };

  return (
    <div>
      <p>The light is {isOn ? 'On' : 'Off'}</p>
      <button onClick={toggleLight}>
        Turn {isOn ? 'Off' : 'On'}
      </button>
    </div>
  );
}

export default LightSwitch;
```
在 `App.js` 中使用它。

---

### Day 5: 處理事件 (Handling Events)

**今日目標：** 學習如何在 React 中處理使用者事件，例如點擊、輸入等。

#### 1. 事件處理
在 React 中處理事件與在原生 DOM 中非常相似，但有幾個語法上的差異：
*   React 事件的命名採用駝峰式命名法 (camelCase)，例如 `onclick` 變成 `onClick`。
*   你傳遞的是一個函式作為**事件處理器 (Event Handler)**，而不是一個字串。

**範例：**
```javascript
// ��遞一個函式
<button onClick={handleClick}>Click Me</button>

// 而不是像 HTML 一樣傳遞字串
// <button onclick="handleClick()">Click Me</button>
```

#### 2. 處理表單輸入
對於表單元素如 `<input>`, `<textarea>`, `<select>`，我們通常使用 `onChange` 事件來監聽其值的變化，並將這個值存入 state。這種模式被稱為**受控元件 (Controlled Component)**。

**範例：一個簡單的輸入框**
```javascript
// src/InputForm.js
import { useState } from 'react';

function InputForm() {
  const [inputValue, setInputValue] = useState('');

  const handleChange = (event) => {
    // event.target.value 包含了 input 的目前值
    setInputValue(event.target.value);
  };

  return (
    <div>
      <p>Your input: {inputValue}</p>
      <input type="text" value={inputValue} onChange={handleChange} />
    </div>
  );
}

export default InputForm;
```
> #### **語法小教室：**
> *   `onChange={handleChange}`：`onChange` 是一個事件屬性，它會在輸入框內容改變時觸發。我們把 `handleChange` 這個函式本身（而不是函式的執行結果）傳給它。
> *   `const handleChange = (event) => { ... }`：這���在定義一個**箭頭函式 (Arrow Function)**，它是 `function handleChange(event) { ... }` 的簡潔寫法。
> *   `event`：當事件發生時，瀏覽器會產生一個**事件物件 (Event Object)**，並將它作為參數傳給我們的處理函式。這個物件包含了事件的相關資訊，例如 `event.target` 指向觸發事件的 DOM 元素（也就是那個 `<input>`），而 `event.target.value` 就是該元素的目前值。

在這個範例中，`<input>` 的值完全由 `inputValue` state 控制。每當使用者輸入時，`onChange` 事件觸發，`handleChange` 函式會更新 state，然後 React 重新渲染元件，將新的值顯示在 `<p>` 標籤和 `<input>` 的 `value` 中。

#### **練習 5**
*   **題目：** 建立一個 `SimpleLoginForm.js` 元件。這個表單包含一個 email 輸入框、一個 password 輸入框和一個提交按鈕。當使用者在輸入框中輸入時，不要做任何事。當使用者點擊提交按鈕時，用 `alert()` 顯示出 email 和 password 的值。
*   **提示：** 你需要為 email 和 password 分別建立 state。在提交按鈕的 `onClick` 事件中處理顯示邏輯。

#### **答案 5**
建立 `src/SimpleLoginForm.js`：
```javascript
// src/SimpleLoginForm.js
import { useState } from 'react';

function SimpleLoginForm() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');

  const handleSubmit = () => {
    alert(`Email: ${email}\nPassword: ${password}`);
  };

  return (
    <div>
      <h3>Login Form</h3>
      <div>
        <label>Email: </label>
        <input 
          type="email" 
          value={email} 
          onChange={(e) => setEmail(e.target.value)} 
        />
      </div>
      <div style={{ marginTop: '10px' }}>
        <label>Password: </label>
        <input 
          type="password" 
          value={password} 
          onChange={(e) => setPassword(e.target.value)} 
        />
      </div>
      <button style={{ marginTop: '10px' }} onClick={handleSubmit}>
        Submit
      </button>
    </div>
  );
}

export default SimpleLoginForm;
```
> #### **語法小教室：**
> *   `onChange={(e) => setEmail(e.target.value)}`：這是一個**內聯 (inline) 的箭頭函式**。當 `onChange` 事件觸發時，這個小函式會被執行，它接收事件物件 `e`，然後呼叫 `setEmail` 並傳入 `e.target.value`。這種寫法很方便，當處理邏輯很簡單時，就��需要另外定義一個獨立的函式。

在 `App.js` 中使用它。

---

### Day 6: 條件渲染 (Conditional Rendering)

**今日目標：** 學習如何根據不同的條件來渲染不同的 UI。

#### 1. 條件渲染技術
在 React 中，你可以使用標準的 JavaScript 語法來進行條件渲染。常見的方法有：
*   `if...else` 陳述式
*   **三元運算子 (Ternary Operator)**: `condition ? trueExpression : falseExpression`
*   **邏輯 `&&` 運算子**: `condition && expression` (如果條件為真，則渲染表達式)

#### 2. 範例
**使用三元運算子**
```javascript
// src/LoginStatus.js
import { useState } from 'react';

function LoginStatus() {
  const [isLoggedIn, setIsLoggedIn] = useState(false);

  return (
    <div>
      <h1>{isLoggedIn ? 'Welcome back!' : 'Please log in.'}</h1>
      <button onClick={() => setIsLoggedIn(!isLoggedIn)}>
        {isLoggedIn ? 'Logout' : 'Login'}
      </button>
    </div>
  );
}
```
> #### **語法小教室：**
> *   `isLoggedIn ? 'Welcome back!' : 'Please log in.'`：這就是**三元運算子**。它的意思是：如果 `isLoggedIn` 是 `true`，則表達式的值為 `'Welcome back!'`；否則（`false`），值為 `'Please log in.'`。它就像一個精簡版的 `if...else`。

**使用邏輯 `&&` 運算子**
當你只想在某個條件成立時才渲染某個元素，`&&` 就非常方便。
```javascript
// src/Mailbox.js
function Mailbox(props) {
  const unreadMessages = props.unreadMessages;
  return (
    <div>
      <h2>Hello!</h2>
      {unreadMessages.length > 0 &&
        <h3>
          You have {unreadMessages.length} unread messages.
        </h3>
      }
    </div>
  );
}
```
> #### **語法小教室：**
> *   `condition && expression`：在 JavaScript 中，這個運算式的行為是：如果 `condition` 為 `true`，則回傳 `expression` 的值；如果 `condition` 為 `false`，則直接回傳 `condition` 的值（在 React 中，`false` 不會被渲染成任何東西）。所以這段程式碼的意思是：「如果 `unreadMessages` 陣列的長度大於 0，那麼就渲染 `<h3>` 這個元素。」

#### **練習 6**
*   **題目：** 建立一個 `LoadingSpinner.js` 元件。這個元件接收一個 `isLoading` 的 prop。如果 `isLoading` 為 `true`，則顯示 "Loading..."；如果為 `false`，則顯示 "Data loaded successfully!"。
*   **提示：** 使用三元運算子是最直接的方法。

#### **答案 6**
建立 `src/LoadingSpinner.js`：
```javascript
// src/LoadingSpinner.js
function LoadingSpinner(props) {
  const { isLoading } = props;

  return (
    <div>
      {isLoading ? <p>Loading...</p> : <p>Data loaded successfully!</p>}
    </div>
  );
}

export default LoadingSpinner;
```
在 `App.js` 中使用它。

---

### Day 7: 列表與 Keys

**今日目標：** 學習如何渲染一個資料列表，並理解 `key` prop 的重要性。

#### 1. 渲染列表
我們通常使用 JavaScript 的 `.map()` 方法來將一個資料陣列轉換成一個 JSX 元素列表。

**範例：**
```javascript
// src/TodoList.js
function TodoList() {
  const todos = [
    { id: 1, text: 'Learn React' },
    { id: 2, text: 'Build a project' },
    { id: 3, text: 'Go to bed' }
  ];

  const todoItems = todos.map(todo =>
    <li key={todo.id}>
      {todo.text}
    </li>
  );

  return <ul>{todoItems}</ul>;
}
```
> #### **語法小教室：**
> *   `todos.map(todo => ...)`：`.map()` 是 JavaScript 陣列的一個方法。它會遍歷陣列中的每一個元素，並對每個元素執行你提供的函式，最後將每次函式執行的回傳值組合成一個**新的陣列**。
> *   在這裡，`map` 函式遍歷 `todos` 陣列。對於每一個 `todo` 物件，��都回傳一個 `<li>` JSX 元素。最終，`todoItems` 會變成一個包含三個 `<li>` 元素的陣列，React 會將它們渲染出來。

#### 2. `key` Prop
`key` 是一個特殊的字串屬性，當你在建立元素列表時需要包含它。

*   **為什麼需要 `key`？** `key` 幫助 React 識別哪些項目被更改、新增或刪除。給予陣列中的元素一個穩定的 `key` 可以極大地提高列表渲染的效能。
*   **如何選擇 `key`？** `key` 在其兄弟節點中必須是**唯一**的。最好的 `key` 是能唯一識別列表項目的 ID，例如資料庫中的 ID。**不建議使用陣列的索引 `index` 作為 `key`**，因為當列表順序改變時，這會導致效能問題和 state 的混亂。

#### **練習 7**
*   **題目：** 建立一個 `ProductList.js` 元件。這個元件接收一個 `products` 陣列作為 prop，`products` 陣列中每個物件包含 `id`, `name`, 和 `price`。元件需要將這個產品列表渲染到畫面上。
*   **提示：** `products` 陣列的範例：`[{ id: 'p1', name: 'Apple', price: 1.2 }, { id: 'p2', name: 'Banana', price: 0.8 }]`。記得為每個列表項目設定唯一的 `key`。

#### **答案 7**
建立 `src/ProductList.js`：
```javascript
// src/ProductList.js
function ProductList(props) {
  const { products } = props;

  if (!products || products.length === 0) {
    return <p>No products available.</p>;
  }

  return (
    <div>
      <h2>Product List</h2>
      <ul>
        {products.map(product => (
          <li key={product.id}>
            {product.name} - ${product.price}
          </li>
        ))}
      </ul>
    </div>
  );
}

export default ProductList;
```
在 `App.js` 中使用它。

---
---

## 第二週：進階概念與實戰

### Day 8: `useEffect` Hook 與副作用

**今日目標：** 學習 `useEffect` Hook 來處理元件中的副作用，例如 API 請求或訂閱。

#### 1. 什麼是副作用 (Side Effects)？
在程式設計中，**副作用**指的是一個函式或表達式除了回傳值之外，還對外部世界產生了可觀察的影響。在 React 元件中，主要任務是根據 props 和 state 渲染 UI。任何超出這個範圍的操作都可以視為副作用。常見的副作用包括：
*   **資料獲取 (Fetching data from an API)**：與遠端伺服器溝通。
*   **設定訂閱 (Setting up a subscription)**：監聽外部事件。
*   **手動更改 DOM**：直接操作瀏覽器畫面（通常不建議）。
*   **設定計時器 (setTimeout, setInterval)**：安排未來的程式碼執行���

#### 2. `useEffect` Hook
`useEffect` 讓你在函式元件中執行副作用操作。它告訴 React：「在元件渲染到畫面上**之後**，請執行這個函式。」

*   **語法：** `useEffect(setup, dependencies?)`
    *   `setup`：一個包含副作用邏輯的函式。
    *   `dependencies` (可選)：一個陣列，包含了 `setup` 函式所依賴的 props 或 state。

**`useEffect` 的執行時機 (由 `dependencies` 陣列決定)：**
1.  **沒有依賴項陣列：** `useEffect(() => { ... })`
    *   在**每次**元件渲染後都會執行（初始渲染和每次更新後）。這種情況較少使用，因為容易造成無限迴圈。
2.  **空的依賴項陣列 `[]`：** `useEffect(() => { ... }, [])`
    *   只在元件第**一次**掛載 (mount) 到畫面上時執行一次。這是執行「只做一次」操作（如 API 請求）的理想選擇。
3.  **有依賴項的陣列 `[dep1, dep2]`：** `useEffect(() => { ... }, [dep1, dep2])`
    *   在第一次掛載時執行，並且只有當陣列中的**任何一個**依賴項 (`dep1` 或 `dep2`) 的值發生變化時，才會再次執行。

#### 3. 清理副作用
有些副作用需要被清理，例如計時器或事件監聽，以避免**記憶體洩漏 (Memory Leak)**。`useEffect` 的 `setup` 函式可以回傳一個**清理函式 (cleanup function)**。這個函式會在元件卸載 (unmount) 前，或在下一次 `useEffect` 執行前被呼叫。

**範例：獲取假資料**
```javascript
// src/DataFetcher.js
import { useState, useEffect } from 'react';

function DataFetcher() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    // 這個 effect 只在元件初次渲染時執行一次
    console.log('Component mounted, fetching data...');
    fetch('https://jsonplaceholder.typicode.com/todos/1')
      .then(response => response.json())
      .then(json => {
        setData(json);
        setLoading(false);
      });
  }, []); // 空陣列表示沒有依賴

  if (loading) {
    return <p>Loading...</p>;
  }

  return (
    <div>
      <h2>Data from API</h2>
      <p>Title: {data.title}</p>
    </div>
  );
}

export default DataFetcher;
```
> #### **語法小教室：**
> *   `useEffect(() => { ... }, []);`：`useEffect` 接收兩個參數：第一個是要執行的函式，第二個是依賴項陣列。
> *   `fetch(...)`：這是瀏覽器內建的函式，用來向網路發送請求。它回傳一個 **Promise**，代表一個非同步操作的最終結果。
> *   `.then(...)`：Promise 的方法。當 `fetch` 成功拿到回應後，第一個 `.then` 會被執行；當 `response.json()` 這個 Promise 解析完成後，第二個 `.then` 會被執行。這就是處理非同步操作的**鏈式呼叫**。

#### **練習 8**
*   **題目：** 建立一個 `Timer.js` 元件。這個元件在畫面掛載後，每秒鐘更新一次計數器並顯示在畫面上。當元件從畫面上移除時，必須清除計時器以避免記憶體洩漏。
*   **提示：** 使用 `setInterval` 來設定計時器，並在 `useEffect` 的清理函式中用 `clearInterval` 來清除它。

#### **答案 8**
建立 `src/Timer.js`：
```javascript
// src/Timer.js
import { useState, useEffect } from 'react';

function Timer() {
  const [seconds, setSeconds] = useState(0);

  useEffect(() => {
    // 設定一個計時器
    const intervalId = setInterval(() => {
      setSeconds(prevSeconds => prevSeconds + 1);
    }, 1000);

    // 清理函式
    return () => {
      console.log('Clearing interval');
      clearInterval(intervalId);
    };
  }, []); // 空陣列確保 effect 和 cleanup 只執行一次

  return (
    <div>
      <h2>Timer: {seconds}s</h2>
    </div>
  );
}

export default Timer;
```
> #### **語法小教室：**
> *   `setSeconds(prevSeconds => prevSeconds + 1)`：這是 `setState` 函式的一種**函式更新**形式。當新 state 需要依賴舊 state 計算時，傳入一個函式是更安全的做法。React 會將前一個 state (`prevSeconds`) 作為參數傳入，確保你拿到的是最新的值。
> *   `return () => { ... }`：在 `useEffect` 的 `setup` 函式中 `return` 一個函式，這個回傳的函式就是**清理函式**。它會在元件要被銷毀時執行，讓我們有機會取消訂閱、清除計時器等。

---

### Day 9: 自訂 Hooks (Custom Hooks)

**今日目標：** 學習如何將可重複使用的邏輯抽離成自訂 Hooks。

#### 1. 為什麼需要自訂 Hooks？
當你在多個元件中發現自己寫了重複的邏輯（例如，同樣的 `useState` 和 `useEffect` 組合來獲取資料或監聽事件），這就是一個將它抽離成自訂 Hook 的好時機。自訂 Hook 的核心精神是**邏輯的複用**。

自訂 Hook 是一個以 `use` 開頭的 JavaScript 函式，它可以呼叫其他的 Hooks。這個命名慣例非常重要，它能讓 React 和開發者識別出這是一個 Hook。

#### 2. 建立一個自訂 Hook
讓我們���昨天獲取資料的邏輯抽離成一個 `useFetch` Hook。

建立 `src/hooks/useFetch.js` (建議將 hooks 放在獨立資料夾)：
```javascript
// src/hooks/useFetch.js
import { useState, useEffect } from 'react';

function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    setLoading(true);
    fetch(url)
      .then(response => response.json())
      .then(json => setData(json))
      .catch(err => setError(err))
      .finally(() => setLoading(false));
  }, [url]); // 依賴 url，當 url 改變時重新獲取

  return { data, loading, error };
}

export default useFetch;
```
> #### **語法小教室：**
> *   `function useFetch(url) { ... }`：這就是一個自訂 Hook。它是一個普通的 JavaScript 函式，但因為它的名字以 `use` 開頭，並且內部呼叫了 `useState` 和 `useEffect`，所以它是一個 Hook。
> *   `return { data, loading, error };`：這個 Hook 回傳一個物件，包含了它所管理的所有狀態。使用這個 Hook 的元件可以方便地解構出它們需要的資料。這是 `{ data: data, loading: loading, error: error }` 的簡寫。

#### 3. 使用自訂 Hook
現在，我們的元件可以變得非��簡潔。
```javascript
// src/UserData.js
import useFetch from './hooks/useFetch';

function UserData({ userId }) {
  const { data, loading, error } = useFetch(`https://jsonplaceholder.typicode.com/users/${userId}`);

  if (loading) return <p>Loading user...</p>;
  if (error) return <p>Error fetching user!</p>;

  return (
    <div>
      <h3>User Details</h3>
      <p>Name: {data.name}</p>
      <p>Email: {data.email}</p>
    </div>
  );
}

export default UserData;
```

#### **練習 9**
*   **題目：** 建立一個 `useDocumentTitle` 的自訂 Hook。這個 Hook 接收一個 `title` 字串，並使用 `useEffect` 來更新瀏覽器分頁的標題 (`document.title`)。
*   **提示：** `useEffect` 的依賴項應該是 `title`。

#### **答案 9**
建立 `src/hooks/useDocumentTitle.js`：
```javascript
// src/hooks/useDocumentTitle.js
import { useEffect } from 'react';

function useDocumentTitle(title) {
  useEffect(() => {
    document.title = title;
  }, [title]); // 當 title 改變時，更新文件標題
}

export default useDocumentTitle;
```
在任何元件中使用它：
```javascript
// src/HomePage.js
import { useState } from 'react';
import useDocumentTitle from './hooks/useDocumentTitle';

function HomePage() {
  const [count, setCount] = useState(0);
  
  // 使用自訂 Hook
  useDocumentTitle(`You clicked ${count} times`);

  return (
    <div>
      <p>Check the document title in your browser tab!</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

export default HomePage;
```

---

### Day 10: React Router

**今日目標：** 學習使用 `React Router` 來建立擁有多個頁面的**單頁應用 (Single-Page Application, SPA)**。

SPA 的特點是整個網站只有一個 HTML 頁面，內容的切換是透過 JavaScript 動態改變畫面，而不是像傳統網站一樣每次都向伺服器請求一個新的 HTML 檔案。這使得使用者體驗更流暢、更像桌面應用程式。

#### 1. 安裝 React Router
```bash
npm install react-router-dom
```

#### 2. 基本設定
在你的 `src/index.js` 或 `src/App.js` 中，用 `BrowserRouter` 包裹你的應用程式。
```javascript
// src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </React.StrictMode>
);
```
> #### **語法小教室：**
> *   `<BrowserRouter>`：這是一個元件，它利用瀏覽器的 History API 來讓你的 UI 與 URL 保持同步。把它放在應用程式的最外層，所有路由相關的功能才能生效。

#### 3. 核心元件
*   `<Routes>`: 包裹所有 `<Route>` 的容器。它會查看所有子 `<Route>`，並渲染第一個路徑匹配目前 URL 的那一個。
*   `<Route>`: 定義一個路由規則。`path` 屬性定義 URL 路徑，`element` 屬性定義該路徑要渲染的元件。
*   `<Link>`: 用來建立導覽連結，類似 HTML 的 `<a>` 標籤，但它會**避免頁面重新整理**，而是直接在內部改變 URL，觸發 `<Routes>` 重新匹配並渲染對應的元件。

#### 4. 範例
建立幾個頁面元件 `HomePage.js`, `AboutPage.js`。
在 `App.js` 中設定路由：
```javascript
// src/App.js
import { Routes, Route, Link } from 'react-router-dom';
import HomePage from './pages/HomePage';
import AboutPage from './pages/AboutPage';

function App() {
  return (
    <div>
      <nav>
        <Link to="/">Home</Link> | {" "}
        <Link to="/about">About</Link>
      </nav>
      <hr />
      <Routes>
        <Route path="/" element={<HomePage />} />
        <Route path="/about" element={<AboutPage />} />
      </Routes>
    </div>
  );
}

export default App;
```

#### **練習 10**
*   **題目：** 在你的應用中增加一個 `ContactPage`。並在導覽列中增加一個指向 `/contact` 的連結。
*   **提示：** 建立 `ContactPage.js` 元件，並在 `App.js` 的 `Routes` 中增加一個新的 `Route`。

#### **答案 10**
建立 `src/pages/ContactPage.js`：
```javascript
// src/pages/ContactPage.js
const ContactPage = () => <h2>Contact Us</h2>;
export default ContactPage;
```
修改 `App.js`。

---

### Day 11: Context API - 全域狀態管理

**今日目標：** 學習使用 React 的 Context API 來避免 "prop drilling" (屬性逐層傳遞)。

#### 1. 什麼是 Prop Drilling？
當一個深層的子元件需要來自高層父元件的資料時，你可能需要將 prop 一層一層地傳遞下去，即使中間的元件根本用不到這個 prop。這個過程就叫做 "prop drilling"。這會讓元件之間的耦合度變高，程式碼難以維護。

想像一下，你��把一個包裹從一樓送到五樓，但你必須親手交給二樓的人，二樓再交給三樓，三樓再交給四樓，最後才到五樓。Context API 就像是安裝了一部電梯，讓你可以從一樓直達五樓。

#### 2. Context API
Context 提供了一種方法，可以讓資料在元件樹中直接傳遞，而無需手動傳遞 props。

**使用步驟：**
1.  **建立 Context:** `const MyContext = React.createContext(defaultValue);`
    *   建立一個 Context 物件。`defaultValue` 是在沒有找到對應 Provider 時的備用值。
2.  **提供 Context:** 使用 `MyContext.Provider` 包裹需要共享資料的元件樹，並透過 `value` prop 提供資料。
    *   所有被 `Provider` 包裹的子元件都能存取到這個 `value`。
3.  **消費 Context:** 在子元件中使用 `useContext(MyContext)` Hook 來讀取資料。
    *   這是最常用、最簡單的消費方式。

#### 3. 範例：主題切換 (Theme Switcher)
建立 `src/context/ThemeContext.js`：
```javascript
// src/context/ThemeContext.js
import { createContext, useState }nfrom 'react';

// 1. 建立 Context
export const ThemeContext = createContext();

// 建立一個 Provider 元件 (這是一個好習慣)
export const ThemeProvider = ({ children }) => {
  const [theme, setTheme] = useState('light');

  const toggleTheme = () => {
    setTheme(prevTheme => (prevTheme === 'light' ? 'dark' : 'light'));
  };

  return (
    // 2. 提供 Context
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
};
```
> #### **語法小教室：**
> *   `({ children })`：`children` 是一個特殊的 prop。它代表了被一個元件包裹的所有子元件。在這裡，`ThemeProvider` 會將它內部包裹的所有內容原封不動地渲染出來。

在 `App.js` 中使用 `ThemeProvider`：
```javascript
// src/App.js
import { ThemeProvider } from './context/ThemeContext';
import MyComponent from './MyComponent';

function App() {
  return (
    <ThemeProvider>
      <MyComponent />
    </ThemeProvider>
  );
}
```
在任何子元件中消費 Context：
```javascript
// src/MyComponent.js
import { useContext } from 'react';
import { ThemeContext } from './context/ThemeContext';

function MyComponent() {
  // 3. 消費 Context
  const { theme, toggleTheme } = useContext(ThemeContext);

  // ...
}
```
> #### **語法小教室：**
> *   `useContext(ThemeContext)`：這個 Hook 會向上尋找元件樹，找到最近的 `ThemeContext.Provider`，並回傳它的 `value` prop。如果找不到，則回傳 `createContext` 時設定的 `defaultValue`。

#### **練習 11**
*   **題目：** 建立一個 `AuthContext` 來管理使用者的登入狀態。這個 Context 應該提供 `isLoggedIn` (布林值) 和一個 `login`/`logout` 函式。建立一個 `LoginPage` 和一個 `ProfilePage`，`LoginPage` 顯示登入按鈕，`ProfilePage` 顯示 "Welcome!" 和登出按鈕。
*   **提示：** 建立 `AuthContext.js`，並在 `App.js` 中使用 `AuthProvider`。根據 `isLoggedIn` 的狀態來決定顯示哪個頁面。

#### **答案 11**
建立 `src/context/AuthContext.js`：
```javascript
// src/context/AuthContext.js
import { createContext, useState, useContext } from 'react';

export const AuthContext = createContext();

export const AuthProvider = ({ children }) => {
  const [isLoggedIn, setIsLoggedIn] = useState(false);
  const login = () => setIsLoggedIn(true);
  const logout = () => setIsLoggedIn(false);

  return (
    <AuthContext.Provider value={{ isLoggedIn, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
};

// 建立一個自訂 Hook 來簡化 Context 的使用
export const useAuth = () => {
  return useContext(AuthContext);
};
```
修改 `App.js`。

---

### Day 12: 表單處理與驗證

**今日目標：** 學習更進階的表單處理技巧，並介紹��個流行的表單庫。

#### 1. 受控元件 (Recap)
我們之前學過受控元件，即表單的狀態由 React state 控制。對於複雜表單，這可能意味著需要管理很多 state。

#### 2. 處理複雜表單
當表單欄位變多時，可以將所有欄位存在一個物件 state 中。
```javascript
const [formData, setFormData] = useState({
  username: '',
  email: '',
  password: ''
});

const handleChange = (e) => {
  const { name, value } = e.target;
  setFormData(prev => ({ ...prev, [name]: value }));
};
```
> #### **語法小教室：**
> *   `...prev`：這是**展開語法 (Spread Syntax)**。`{ ...prev }` 會建立一個 `prev` 物件的淺拷貝。
> *   `[name]: value`：這是**計算屬性名稱 (Computed Property Names)**。它允許我們用一個變數（這裡的 `name`，值可能是 "username" 或 "email"）來當作物件的屬性名稱。這使得我們可以只用一個 `handleChange` 函式來處理所有輸入框的變更。

#### 3. 介紹 `React Hook Form`
對於需要複雜驗證和狀態管理的表單，手動處理會變得很繁瑣。`React Hook Form` 是一個高效能、易於使用的表單庫，它將表單狀態的管理從 React state 中分離出來，減少了不必要的重新渲染，提升了效能。

**安裝：**
```bash
npm install react-hook-form
```

**範例：**
```javascript
// src/MyForm.js
import { useForm } from 'react-hook-form';

function MyForm() {
  const { register, handleSubmit, formState: { errors } } = useForm();

  const onSubmit = (data) => {
    console.log(data);
    alert('Form submitted successfully!');
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      {/* ... */}
    </form>
  );
}
```
> #### **語法小教室：**
> *   `useForm()`：`react-hook-form` 的核心 Hook，回傳一個包含多個有用方法和狀態的物件。
> *   `register('username', { ... })`：這個函式用來「註冊」一個輸入框。它會回傳 `onChange`, `onBlur`, `name`, `ref` 等屬性，你需要用展開語法 `{...}` 將它們應用到 `<input>` 上。第二個參數是驗證規則物件。
> *   `handleSubmit(onSubmit)`：這是一個高階函式。你把你的提交邏輯函式 (`onSubmit`) 傳給它，它會先觸發表單驗證，只有在驗證通過後，才會呼叫你的 `onSubmit`，並傳入表單資料 `data`。
> *   `errors`：一個物件，包含了所有驗證失敗的欄位及其錯誤訊息。

#### **練習 12**
*   **題目：** 使用 `React Hook Form` 建立一個註冊表單，包含 `firstName` (必填) 和 `age` (必填，且必須是數字) 兩���欄位。
*   **提示：** 使用 `register` 的第二個參數來設定驗證規則，例如 `required` 和 `valueAsNumber`。

#### **答案 12**
建立 `src/RegistrationForm.js`。

---

### Day 13 & 14: 專案實戰 - Todo List App

**目標：** 綜合運用前兩週所學的知識，建立一個功能完整的 Todo List 應用程式。

**功能需求：**
1.  顯示待辦事項列表。
2.  可以新增待辦事項。
3.  可以將待辦事項標記為已完成/未完成。
4.  可以刪除待辦事項。
5.  (進階) 將待辦事項儲存在瀏覽器的 `localStorage` 中，這樣重新整理頁面後資料不會消失。
6.  (進階) 可以篩選顯示「全部」、「已完成」、「未完成」的待辦事項。

> #### **語法小教室：`localStorage`**
> *   `window.localStorage` 是瀏覽器提供的一個儲存機制，可以讓你用鍵值對 (key-value pairs) 的形式儲存**字串**資料。
> *   `localStorage.setItem(key, value)`：儲存資料。
> *   `localStorage.getItem(key)`：讀取資料。
> *   `JSON.stringify(object)`：將 JavaScript 物件或陣列轉換成 JSON 字串，以便儲存。
> *   `JSON.parse(string)`：將 JSON 字串解析回 JavaScript 物件或陣列。

#### **專案結構建議**
```
src/
├── components/
│   ├── TodoForm.js     # 新增待辦事項的表單
│   ├── TodoList.js     # 顯示列表
│   └── TodoItem.js     # 單個待辦事項
├── hooks/
│   └── useLocalStorage.js # (進階) 用於 localStorage 的自訂 Hook
└── App.js              # 主應用程式元件
```

#### **Day 13: 核心功能開發**
**練習 13：**
1.  在 `App.js` 中使用 `useState` 管理 `todos` 陣列。
2.  建立 `TodoForm.js`，包含一個輸入框和一個新增按鈕。當表單提交時，呼叫從 `App.js` 傳入的函式來新增一個 todo。
3.  建立 `TodoList.js`，接收 `todos` 陣列並使用 `.map()` 渲染出 `TodoItem` 元件列表。
4.  建立 `TodoItem.js`，顯示單個 todo 的內容，並包含一個「完成」按鈕和一個「刪除」按鈕。點擊按鈕時，呼叫從 `App.js` 傳入的對應函式。

#### **Day 14: 進階功能與優化**
**練習 14：**
1.  建立 `useLocalStorage.js` 自訂 Hook，它和 `useState` 很像，但會自動將 state 同步到 `localStorage`。
2.  在 `App.js` 中用 `useLocalStorage` 替換 `useState` 來儲存 `todos`。
3.  增加篩選功能。在 `App.js` 中增加一個 state 來管理目前的篩選條件 (`'all'`, `'completed'`, `'active'`)，並根據這個 state 來過濾要傳遞給 `TodoList.js` 的 `todos` 陣列。

---

#### **專案參考答案**
(參考答案程式碼省略)

---

### 恭喜你！
你已經完成了為期兩週的 React 學習之旅！你從零開始，掌握了 React 的核心概念，並親手打造了一個完整的應用程式。

**接下來的學習方向：**
*   **TypeScript:** 為你的 React 專案增加型別安全。
*   **狀態管理庫:** 學習 Redux Toolkit 或 Zustand 來管理更複雜的應用程式狀態。
*   **React 框架:** 探索 Next.js (用於伺服器渲染和靜態網站生成) 或 Remix。
*   **測試:** 學習使用 Jest 和 React Testing Library 來為你的元件編寫測試。
*   **元件庫:** 熟悉 Material-UI (MUI) 或 Ant Design 等流行的 UI 元件庫，加速你的開發流程。

繼續保持學習的熱情，你將在前端開發的道路上越走越遠！