---
# System prepended metadata

title: 程式30天挑戰

---

# 程式30天挑戰
> 為了讓自己累積能夠獨立做出來的自信於是開始了30天挑戰
> 連續30天每天push一點點，重點不是【夠強】而是一直有進步
> 筆記會以教朋友寫程式的口吻撰寫，歡迎大家參考與指教
> 這份筆記希望讓跟我一樣懷疑自己能不能轉職的人，也能獲得相信自己的骨氣

---
## 🗓️ 程式 30 天挑戰進度
| 日期  | Day   | 主題                                |
| ----- | ----- | ----------------------------------- |
| 04/21 | Day 1 | 開發環境建立                        |
| 04/22 | Day 2 | 主頁路由按鈕+複習useState，做計數器 |
| 04/23 | Day3  | 設計資料結構、陣列產生按鈕          |
| 04/24 | Day4  | 互動功能進階(計算機)                |
| 04/25 | Day5  | 計算機UI設計                        |
| 04/26 | Day6  | 面試題練習(受控元件vs不受控元件)    |
| 04/27 | Day7  | 小總整理日 (補齊Day5、6筆記)        |
| 04/28 | Day8  | 計算機功能                          |
| 04/29 | Day9  | 支援鍵盤輸入                       |
| 04/30 | Day10 | 休息日                              |
| 05/01 | Day11 | JAVA倒敘練習                         |
| 05/03 | Day12 |   日語會話練習                      |
| 05/04  | Day13      |  小整理日(計算機功能筆記)                                   |
|       |       |                                     |




---

## day1 安裝程式+專案初始化
下載[vs code](https://code.visualstudio.com/)和[node.js](https://nodejs.org/zh-tw/download)
node.js跑完安裝程序後在終端機輸入`node -v npm -v`如果出現版本號即表示安裝成功
vs code安裝好後可以安裝套件改成中文版(減少心理壓力 哈哈) 安裝教學可參考[卡斯伯老師的筆記](https://www.casper.tw/development/2019/12/01/vscode-chinese/#google_vignette)

安裝好之後就可以來將檔案初始化啦~

**step1.** 打開你的終端機 並且輸入 `npm create vite@latest` 安裝vite
**step2.** 這次專案的基本資訊是選擇 React + JavaScript，然後依照指示繼續（就一路 Enter～）
**step3.** 輸入 **`cd 專案資料夾、npm install、npm run dev`** 打開伺服器看環境是否建立成功
**step4.** 🎯小任務時間:將預設內容清除，寫一句專屬於你的歡迎詞
**step5.** 若伺服器畫面內容有跟著更動，就完成囉!! 今天也是很棒的一天

🚩**今天的程式碼統整:**

| 功能       | 程式碼                                  | 打在哪? |
| ---------- | --------------------------------------- | ------- |
| 確認node.js成功安裝 | node -v npm -v    |  終端機       |
| 安裝vite   | npm create vite@latest                  | 終端機  |
| 打開伺服器 | cd 專案資料夾、npm install、npm run dev | 終端機  |

🔔 **給不小心出錯的我們**
1. node.js 不要直接從官網首頁下載，會變成壓縮檔很麻煩，從下載頁面(我上面貼的連結)下載會最順利，另外下載任何程式檔案時盡量都下載LTS版本比較不會出問題

1. 下載好之後要重新打開終端機再下`node -v npm -v` 才會吃到新的資料

1. 不小心輸錯指令的時候只要重輸一次就好囉，不過vite現在沒有支援回上一步的功能，所以不小心輸錯就要重新下`npm create vite@latest`囉


> 🧠 **今天的心得**:原本以為從安裝程式開始建置環境會很麻煩，沒想到很快就完成了，超級讚!
> 
> 🕐**明天目標**:複習useState，製作計數器

---
## day2 主頁路由按鈕+計數器練習
> 為了讓後續挑戰可以快速從主頁按鈕進入今天先做了路由按鈕

### day2-1 路由按鈕

**step1.** 在終端機輸入 **npm i react-router-dom** 下載路由套件
**step2.** 設置元件 **src>days>Day2.jsx**
**step3.** 在days資料夾下新增index.jsx統一匯出所有元件
**step4.** 設置路由 新增 **src>routes>index.jsx** 
```
import App from "../App";
import { Day2 } from "../days/index.jsx";
import { createBrowserRouter, createHashRouter } from "react-router-dom";

const routes = [{
    path: "/",
    element: <App />,
}, {
    path: "/day2",
    element: <Day2 />
}];

{/* 判斷是否為開發狀態決定使用哪一種路由*/}
const router = import.meta.env.MODE === "development"
    ? createBrowserRouter(routes)
    : createHashRouter(routes);

export default router;
```
**step5.** 在 main.jsx **import { RouterProvider } from 'react-router-dom'**，並將原本的<App/> 改為 **<RouterProvider router={router}></RouterProvider>**

**step6.** 在App.jsx **import Outlet, useNavigate**，並使用useNavigate設定按鈕路由，

```
{/* 記得要import */}
import { Outlet, useNavigate } from 'react-router-dom'
import './App.css'

function App() {
{/* 宣告useNavigate方法 */}
  const navigate = useNavigate();

  return (
    <>
      <h1>React30天挑戰!</h1>
      <button type="button" onClick={()=>navigate('/day2') }>day2</button>
      {/* 要記得加上()=> 不然會直接執行變成無窮迴圈喔 */}
      <Outlet></Outlet>
    </>
  )
}

export default App

```
完成以上6步驟，就設定好day2的路由啦🎉

🔔 **給不小心出錯的我們**
1. 開始設定路由前，先確定有下載過**react-router-dom** 避免電腦找不到而報錯
2. Copilot 很好用，但它有時會「忘了幫你 import」～看到報錯先別慌，第一步就是檢查 import！ 如:Outlet, useNavigate ...等
3. import.meta.env.MODE 的MODE 一定要記得全大寫(固定寫法)

---
### day2-2 計數器


### 🧪 畫面預期效果

- 顯示當前數字與點擊次數
- 三個按鈕（+1、+10、+20）可以點擊加數字
- 數字超過 100 後顯示達成訊息

**step1.** 
```
//宣告 前面為變數，後面是控制方法，useState要記得import
const [number,setNumber] = useState(0);
const [count,setCount] = useState(0);
```
**step2.** 
```
return (
        <>
            <h1>Day 2 計數器練習</h1>
            {/*文字顯示 當前加總的數字及點擊總次數 */}
            <p>目前的數字{number}</p>
            <p>目前點擊次數{count}</p>
            {/* 點擊按鈕時讓原本的數字+n(自己設定) ，count+1(多點擊一次)*/}
            <button onClick={()=>{setNumber(number+1);setCount(count+1)}}>按我+1</button>
            <button onClick={()=>{setNumber(number+10);setCount(count+1)}}>按我+10</button>
            <button onClick={()=>{setNumber(number+20);setCount(count+1)}}>按我+20</button>
        </>
        {/*當number>100時出現此文字*/}
        {number >100 && <p>你花了{count}次，讓數字大於100</p>}
    )
```

**step3.** 將重複的方法抽出來做成函式
```
const handleClick = (num)=>{
        setNumber(number+num);
        setCount(count+1);
    }

//button寫法改為，10為自訂數字
 <button onClick={()=>{handleClick(10)}}>
```

完整版:
```
import { useState } from "react";

export default function Day2() {
    const [number,setNumber] = useState(0);
    const [count,setCount] = useState(0);
    const handleClick = (num)=>{
        setNumber(number+num);
        setCount(count+1);
    }
    return (
        <>
            <h1>Day 2 計數器練習</h1>
            <p>目前的數字{number}</p>
            <p>目前點擊次數{count}</p>
            <button onClick={()=>{handleClick(1)}}>按我+1</button>
            <button onClick={()=>{handleClick(10)}}>按我+10</button>
            <button onClick={()=>{handleClick(20)}}>按我+20</button>

            {number >100 && <p>你花了{count}次，讓數字大於100</p>}
        </>
    )
}

```

🔔 **給不小心出錯的我們**
1. onClick{()=>}，()=>一定要寫 避免造成無窮迴圈而報錯
2. 改成函式寫法時()內記得填入數字，否則電腦會不知道要加多少而出錯

> 🧠 **今天的心得**:一開始連hook使用時機都看得霧煞煞沒想到現在可以輕鬆自在的用useState寫出計數器了! 回頭看才發現自己原來也走了很遠了!
> 
> 🕐**明天目標**:
> - 設計資料結構（例如：day、日期、完成狀態）
> - 使用 `.map()` 或其他陣列方法來產生多顆按鈕

**day3新增功能:** 自訂數字
```
const [customNum,setCustomNum] = useState(0);
 
<input type="number" name="customNum" value={customNum} min="1" onChange={(e)=>setCustomNum(Number(e.target.value))} />
            <button onClick={()=>handleClick(customNum)}>確定</button>
```

---
## day3 使用陣列資料產生按鈕

**step1.** 宣告資料結構(放在App.jsx)
```
 const daysData=[
    {day:1,label:"Day1:環境建置",path:"" },
    {day:2,label:"Day2:計數器練習",path:"/day2"},
    {day:3,label:"Day3:陣列產生按鈕",path:""},
    {/*後續可再新增*/}
];
```

**step2.** 使用.map產生按鈕
```
{/*如果該物件沒有path(!day.path) 則讓按鈕disabled 並且顯示"這一天沒有頁面"*/}
{daysData.map((day)=>
        <button key={day.day} onClick={()=>navigate(day.path)} disabled={!day.path} style={{
          margin: '4px',
          opacity: !day.path ? 0.5 : 1,
          cursor: !day.path ? 'not-allowed' : 'pointer'
        }}
        title={!day.path ? "這一天沒有頁面" : `Day ${day.day}`}>{day.label}</button>)}
```

🔔 **給不小心出錯的我們**
1. 就算該物件沒有path，也要打上空字串""
2. disabled很好用 寫法可以記一下**disabled={!day.path}**
1. 要在字串的地方放變數要使用樣板字面值  **`Day ${day.day}`**
1. key要記得使用唯一值

> 🧠 **今天的心得**:今天建立資料也建立得很快，並且透過.map去產生按鈕可以快速生成很方便，是個很好用的方法
> 
> 🕐**明天目標**:
> - 使用者輸入數字後可以「加」或「減」
> - 按下按鈕後清空輸入框
> - 防呆：輸入為空或 0 時不能按
> - Reset 重置所有數字與點擊次數
> - 顯示使用者目前進行了幾次互動

---

## day4 計算機

**step1.** 設定初始值
```
    const [num1, setNum1] = useState(0);
    const [num2, setNum2] = useState(0);
    const [result, setResult] = useState(0);
    const calculate = [
        { id: 1, sign: "+", label: "加" },
        { id: 2, sign: "-", label: "減" },
        { id: 3, sign: "*", label: "乘" },
        { id: 4, sign: "/", label: "除" },
    ];
```

**step2.** 處理運算的方法
```
const handleCalculate = (num1, num2, sign) => {
        num1 = Number(num1);
        num2 = Number(num2);
        switch (sign) {
            case "+":
                return num1 + num2;
            case "-":
                return num1 - num2;
            case "*":
                return num1 * num2;
            case "/":
                return num1 / num2;
            default:
                return 0;
        }
    }
```

**step3.** 設定兩個數字input
```
<h1>計算機</h1>
<input type="number"  name="num1" value={num1} onChange={(e) => { setNum1(e.target.value) }} style={{ width: '50px', }} />
<input type="number"  name="num2" value={num2} onChange={(e) => { setNum2(e.target.value) }} style={{ width: '50px', }} />
```

**step4.** 使用.map陣列方法產生按鈕
```
{calculate.map((item) =>
    <button type="button" key={item.id} 
    onClick={() => item.sign !== "/" ? setResult(handleCalculate(num1,num2,item.sign)) :
    setResult(num2 ? handleCalculate(num1,num2,item.sign) : "除數不得為0")} >
    {item.label}</button>
            )}
```

**step5.** 結果顯示與重置
```
<p>結果{result}</p>
<button type="button" onClick={()=>setResult(0)}>Reset</button>
```

🔔 **給不小心出錯的我們**
1. 寫新元件的時候要記得 **return**還有到index匯出、新增路由
2. 三元運算子很好用! 可以省去複雜的if、else結構

> 🧠 **今天的心得**:三元運算子很好用
> 
> 🕐**明天目標**:
> - 改善畫面設計
> - 連續加總功能 

---
## Day5 計算機介面
**step1.** 使用陣列存放按鈕
```
//使用二維陣列建立數字鍵盤，每個子陣列都是一行按鈕
    const numbers =[
        [7,8,9],
        [4,5,6],
        [1,2,3],
        [0,"c","="]
    ]
    
//運算符按鈕
const operators = [
        {index:0 ,sign:"+"},
        {index:1 ,sign:"-"},    
        {index:2 ,sign:"*"},
        {index:3 ,sign:"/"},
  ]
```
**step2.** 使用巢狀.map()產生數字鍵盤
```
{/* 使用巢狀.map()產生數字鍵盤，外層處理行，內層處理每顆按鈕 */}
 {numbers.map((row,rowIndex)=>(
    <div key={rowIndex} className="d-flex gap-1">
        {row.map((label)=>(
            <button type="button" className="btn btn-secondary m-1" key={label} >{label}</button>
                ))}
            </div>
        ))}
```
**step3.** 使用.map()產生運算符按鈕
```
{ operators.map((sign)=>(
               <button type="button" className="btn btn-secondary" key={sign.index}>{sign.sign}</button>
           ))}
```
**step4.** 加上flex排版(完整版程式碼)
> 最外層從 flex-column 換成 **flex-row**，
> 才能讓數字區和運算子區並排

```
 <><h1>Day5 計算機進階</h1>
            {/* 最上面顯示輸入與結果的地方 */}
            <div className="input" >{input}</div>
            <br />
            {/* 使用巢狀.map()產生數字鍵盤，外層處理行，內層處理每顆按鈕 */}
            <div className="d-flex flex-row  align-items-center justify-content-center gap-2 mt-1">

                <div className="d-flex flex-column">
                    {numbers.map((row, rowIndex) => (
                        <div key={rowIndex} className="d-flex gap-1">
                            {row.map((label) => (
                                <button type="button" className="btn btn-secondary m-1" key={label} >{label}</button>
                            ))}
                        </div>
                    ))}
                </div>

                {/* 使用.map()產生運算子按鈕 */}
                <div className="d-flex  flex-column gap-2">
                    {operators.map((sign) => (
                        <button type="button" className="btn btn-secondary" key={sign.index}>{sign.sign}</button>
                    ))}
                </div>

            </div>
        </>
```

🔔 **給不小心出錯的我們**
1. 巢狀.map()略為複雜要再多看幾次
2. flex排版要多留意排版方法

> 🧠 **今天的心得**:感覺這幾天寫下來對.map()的方法更清楚了，flex真的是一個神奇的東西，原本排不出想要的樣式，但多調整一下又完成了
> 
![螢幕擷取畫面 2025-04-26 001426](https://hackmd.io/_uploads/S1HwJCo1gx.png)

---
## Day6 面試題練習(受控元件vs非受控元件)
> 在此感謝我的AI助手小暖，陪我無壓力的練習面試題

**受控元件：**
使用 useState 即時偵測使用者輸入的內容。
但因為使用者每打一次字就會觸發一次 render，
當輸入速度很快、或者表單規模較大時，容易造成網頁效能負擔。
可以透過 debounce 或 throttle 的方式來延遲或限制 re-render 的條件與頻率，提升效能。

**範例:**
```
// 受控元件例子-使用useState控制
const [value, setValue] = useState("");
<input value={value} onChange={(e) => setValue(e.target.value)} />
```

**非受控元件：**
通常會使用在整合第三方套件，或希望降低 re-render 次數的場景。
不會即時偵測輸入內容，而是透過 useRef 直接取得 DOM 的資料，
等到需要的時候一次性讀取。
**範例:**
```
// 非受控元件例子-使用useRef控制
const inputRef = useRef();
<input ref={inputRef} />
```

延伸參考: [iT邦幫忙](https://ithelp.ithome.com.tw/articles/10297302#:~:text=%E9%9D%9E%E5%8F%97%E6%8E%A7%E5%85%83%E4%BB%B6%20%E7%9A%84%E5%84%AA%E9%BB%9E%E6%98%AF%E9%9D%9E%E5%B8%B8%E7%B4%94%E7%B2%B9%20%28%E4%B8%8D%E6%B7%BB%E5%8A%A0%E4%BB%BB%E4%BD%95%E5%A4%96%E4%BE%86%E7%89%A9%E8%B3%AA%29%EF%BC%8C%E5%8F%AA%E8%A6%81%E6%8A%8A%E5%80%BC%E4%B9%96%E4%B9%96%E6%94%BE%E5%9C%A8%E8%87%AA%E5%B7%B1%E8%BA%AB%E4%B8%8A%EF%BC%8C%E4%B8%8D%E7%94%A8%E9%9A%A8%E6%99%82%E6%9B%B4%E6%96%B0%E5%88%B0%20state%20%E4%B8%8A%EF%BC%8C%E5%9B%A0%E6%AD%A4%20%E6%95%B4%E9%AB%94%E6%95%88%E8%83%BD%E4%BE%86%E8%AA%AA%E6%98%AF%E8%BC%83%E5%A5%BD%E7%9A%84%E3%80%82,%E5%8F%97%E6%8E%A7%E5%85%83%E4%BB%B6%20%E7%9A%84%E5%84%AA%E9%BB%9E%E6%98%AF%EF%BC%8C%20%E5%B0%8D%E6%96%BC%E8%A1%A8%E5%96%AE%E7%9A%84%E6%8E%8C%E6%8E%A7%E8%83%BD%E5%8A%9B%E6%8F%90%E9%AB%98%EF%BC%8C%E5%9B%A0%E7%82%BA%E9%9A%A8%E6%99%82%E5%8F%AF%E4%BB%A5%E7%9F%A5%E9%81%93%E6%AF%8F%E5%80%8B%E6%AC%84%E4%BD%8D%E5%8D%B3%E6%99%82%E7%9A%84%E6%95%B8%E5%80%BC%EF%BC%8C%E5%B0%B1%E5%8F%AF%E4%BB%A5%E5%81%9A%E5%88%B0%E5%83%8F%E6%98%AF%E3%80%8CA%20%E6%AC%84%E4%BD%8D%E5%A6%82%E6%9E%9C%E9%81%B8%E4%BA%86%E4%BB%80%E9%BA%BC%EF%BC%8CB%20%E6%AC%84%E4%BD%8D%E5%B0%B1%E6%80%8E%E6%A8%A3%E3%80%8D%E9%80%99%E7%A8%AE%20UI%20%E6%96%B9%E9%9D%A2%E7%9A%84%E4%BA%A4%E4%BA%92%E9%97%9C%E4%BF%82%E3%80%82)、[React文件](https://zh-hant.legacy.reactjs.org/docs/uncontrolled-components.html)

> 🧠 **今天的心得**:發現自己還有很多理論上的知識沒有理解的很透徹，之後要再多花一些時間認真準備面試題

---
Day7: 小總整理日 
今日內容:整理並補齊 Day5、Day6 的筆記。
> 🧠 **今天的心得**:雖然今天沒有練習新技能，也沒有推進新的挑戰，但我喜歡這樣用自己的步調，一步一步穩穩前進的感覺。
比起一次爆衝、後繼無力，慢慢累積的成就感，反而更讓我想繼續走下去。


---

### Day8 計算機功能

> 上次只有做出計算機按鈕但無法點擊進行運算，今天我們要做的事情是讓計算機可以真的運作起來

**step1.** 先宣告input、isResult的useState存放狀態
```
 const [input, setInput] = useState("");
 const [isResult, setIsResult] = useState(false);
```

**step2.** 分別撰寫四則運算的函式 (add、substract、multiply、divide )

```
const add = (a, b) => a + b;
const substract = (a, b) => a - b;
const multiply = (a, b) => a * b;
const divide = (a, b) => {
    if (b === 0) {
    //除式要多判斷除數不得為0
    //如果除數為0，則顯示錯誤訊息，並返回"錯誤"
      alert("除數不能為0");
      return "錯誤";
    }
    return a / b;
  };
```

**step3.** 建立函式尋找運算符 (findOperator)
> 用.find去尋找第一個符合 『input裡包含的運算符』這個條件的資料並回傳

```
// 回傳找到的第一個符號的 sign 屬性（如果沒有則為 undefined）
 const findOperator = (input) => {
    return operators.find((op) => input.includes(op.sign))?.sign;
  };
```

**step4.** 建立函式處理運算過程 （handlePress）

```
//將數字、運算符傳入計算函式，並根據運算符進行計算
//最後返回計算結果
const handleCalculate = (num1, num2, operator) => {
        switch (operator) {
            case "+":
                return add(num1, num2);
            case "-":
                return substract(num1, num2);
            case "*":
                return multiply(num1, num2);
            case "/":
                return divide(num1, num2);
            default:
                return 0;
        }
    };
```

**step4.** 建立函式處理運算事件邏輯

**step4-1.** 宣告運算符陣列，並取得最後一個字元
```
const signs = ["+", "-", "*", "/"];
const lastChar = input[input.length - 1];
```

**step4-2.** 按下等號時進行運算

```
if (label === "=") {
    //使用findOperator函式尋找運算符
    //如果沒有找到運算符，則顯示錯誤訊息
        const operator = findOperator(input);
          if (!operator) {
            alert("請輸入正確算式");
            return;
          }
          

     //如果找到運算符，則將字串分割成兩個數字
     //並將兩個數字與運算符傳進入handleCalculate函式進行計算，並儲存成result
         const [num1, num2] = input.split(operator);
         const result = handleCalculate(Number(num1), Number(num2), operator);
      
    //處理除數為0的情況，在divide函式中我們有將除數為0的情況回傳為 "錯誤"
    //如果resuult為"錯誤"，則跳出錯誤提示
    
          if (result === "錯誤") {
            alert("除數不能為0");

         //尋找運算符在字串中的最後一個index位置
            const operatorIndex = input.lastIndexOf(operator);

        //並將運算符前的字串取出，將input重新設置為這個字串
        //為了保留之前正確輸入的數字，只清除運算符與之後的錯誤部分
            const newInput = input.slice(0, operatorIndex);
            setInput(newInput);

        //跳出 if (label === "=")這段判斷式
            return;
          }
      
        //將計算設為字串，設為input值
          setInput(result.toString());

        //設置isResult為true，表示計算結果已經顯示
          setIsResult(true);

        //跳出 if (label === "=")這段判斷式
          return;
        }
```

**step4-3.** 數字處理
```
//檢查目前按下的是不是數字/\d/為正規表達式的數字表示方法
    if (/\d/.test(label)) {
            //如果是數字鍵且已有結果，並且最後的字元不是運算符
          if (isResult && !signs.includes(lastChar)) {
            // 計算結果後重新開始
            setInput(label); 
          } else {
            //否則將數字繼續接在 input 後面
            setInput(prev => prev + label);
          }
          //將isResult設為false，表示還沒產生結果
          setIsResult(false);
          return;
        }
```

**step4-4.** 運算符處理

```
//判斷目前按的鍵是不是運算符
    if (signs.includes(label)) {
        
        //如果input的最後一個字元是運算符號，就會將最後一個字元刪除，並換成目前按下的鍵
          if (signs.includes(lastChar)) {
            setInput(prev => prev.slice(0, -1) + label); 
          } else {
            //如果最後一個字元不是運算符，則會直接將目前按下的鍵加在input後面
            setInput(prev => prev + label);
          }
           //將isResult設為false，表示還沒產生結果
          setIsResult(false);
          return;
        }
```

**step4-5.** 處理清空鍵與倒退鍵

```
// 清除輸入
//如果目前按下的是"c"或"C"，則會將input清空，並將isResult設為false
 if (label === "c" || label === "C") {
            setInput("");
            setIsResult(false);
            return;
          }
        
          // 倒退鍵
          //如果目前按下的是 "←"，則會清除最後一個字元
          if (label === "←") {
            setInput(prev => prev.slice(0, -1));
            setIsResult(false);
            return;
          }
```

### **完整版**
```
 const handlePress = (label) => {
        
        const signs = ["+", "-", "*", "/"];
        const lastChar = input[input.length - 1];   
        
       
        if (label === "=") {
          const operator = findOperator(input);
          if (!operator) {
            alert("請輸入正確算式");
            return;
          }

          const [num1, num2] = input.split(operator);
          const result = handleCalculate(Number(num1), Number(num2), operator);
      
          
          if (result === "錯誤") {
            alert("除數不能為0");
            const operatorIndex = input.lastIndexOf(operator);
            const newInput = input.slice(0, operatorIndex);
            setInput(newInput);
            return;
          }
 
          setInput(result.toString());
          setIsResult(true);
          return;
        }
      
        if (/\d/.test(label)) {
          if (isResult && !signs.includes(lastChar)) {
            setInput(label); 
          } else {
            setInput(prev => prev + label);
          }
          
          setIsResult(false);
          return;
        }
      
        if (signs.includes(label)) {
          if (signs.includes(lastChar)) {
            setInput(prev => prev.slice(0, -1) + label); 
          } else {
            setInput(prev => prev + label);
          }
           
          setIsResult(false);
          return;
        }

       
        if (label === "c" || label === "C") {
            setInput("");
            setIsResult(false);
            return;
          }
        
          if (label === "←") {
            setInput(prev => prev.slice(0, -1));
            setIsResult(false);
            return;
          }
      };
```

> 🧠 **今天的心得**:一開始只是因為compilot的自動提示一起興起寫了計算機的程式，沒想到比想像中還複雜很多，但也因此學到了不少，像是處理陣列、字串、判斷式等等的知識

---
### Day9 計算機(支援鍵盤)
> 繼承Day8內容，再新增支援鍵盤輸入功能

**step1.** 使用useEffect處理按鍵事件

```
useEffect(() => {
        const handleKeyDown = (e) => {
            const key = e.key;

            if (/\d/.test(key)) {
                // 如果是數字 0-9
                handlePress(key);
            } else if (["+", "-", "*", "/"].includes(key)) {
                handlePress(key);
            } else if (key === "Enter") {
                handlePress("=");
            } else if (key.toLowerCase() === "c") {
                handlePress("c");
            } else if (key === "Backspace") {
                handlePress("←"); // 自訂倒退鍵（我們下一步會處理這個）
            }
        };

        window.addEventListener("keydown", handleKeyDown);

        return () => {
            window.removeEventListener("keydown", handleKeyDown);
        };
    }, [handlePress]);
```

**step1-1.** 撰寫handleKeyDown函式
```
 const handleKeyDown = (e) => {
            const key = e.key;

            if (/\d/.test(key)) {
                // 如果是數字 0-9
                handlePress(key);
            } else if (["+", "-", "*", "/"].includes(key)) {
                handlePress(key);
            } else if (key === "Enter") {
                handlePress("=");
            } else if (key.toLowerCase() === "c") {
                handlePress("c");
            } else if (key === "Backspace") {
                handlePress("←"); // 自訂倒退鍵
            }
        };

```
這段程式會根據使用者按下的鍵盤按鍵內容（e.key），對應呼叫 handlePress：

**step1-2.** 加入監聽事件

```
window.addEventListener("keydown", handleKeyDown);

        return () => {
            window.removeEventListener("keydown", handleKeyDown);
        };
```
當按下鍵盤時觸發handleKeyDown函式
並在處理完後使用`return`將其移除避免佔用記憶體空間
SPA（單頁應用）中切換頁面時，也可以避免監聽未移除產生錯頁邏輯


**step1-3.** 當handlePress函式更新，就要觸發這段useEffect


`[handlePress]`
useEffect([...]) 中的陣列是依賴清單。
當裡面的變數(這裡是handlePress)變動時，重新執行uesEffect的邏輯
確保最新的變數被正確綁定，跟著變數進行更新，不會用到舊邏輯

**step2.** 在handlePress加上useCallBack
> 為了避免每次re-render都產生一份新的函式，所以使用useCallback 將它記憶起來。
> 只有當useCallBack裡的依賴變數改變時函式才會被重新建立

```
 const handlePress = useCallback((label) => {
//原始函式內容，參照day5

    },[input, isResult, operators]);
```
* useCallback 適合記憶「函式」
* 若該函式會被 useEffect 或其他 hook 使用，就應該透過 useCallback 穩定引用
* 依賴陣列中一樣是放入在useCallback中有用到的變數條件

**step3.** 使用useMemo記住運算子的陣列值
```
const operators =useMemo(()=> [
            { index: 0, sign: "+" },
            { index: 1, sign: "-" },
            { index: 2, sign: "*" },
            { index: 3, sign: "/" },
            { index: 4, sign: "←" },
        ]
    ,[] )
```
因為operators是一組固定不變的值，如果直接放在元件裡面，每次 re-render 都會被重新建立，導致依賴它的函式(這裡是handlePress)會不斷被重建，因此必須要使用useMemo記憶起來，確保他在整個生命週期中都不會變

> useMemo 是記憶「資料」，useCallback 是記憶「函式」，兩者常搭配使用來穩定 React 組件的邏輯與效能。

> 🧠 今天的心得:當初只是照著做沒想到有那麼多眉眉角角，隨著做完筆記也跟著疏通了一些觀念。
