跳至主要内容

常見 Web Frontend 前端工程師面試準備題目清單

· 閱讀時間約 15 分鐘
KD Chang
Maker, Curator & Lifelong learner
  1. 掌握基礎知識:在開始解決複雜問題之前,您需要確保對前端開發的基礎知識有扎實的理解,包括 HTML、CSS 和 JavaScript,以及它們如何協作來創建響應式和互動式的網頁。如果您認為自己在這些主題上仍需學習,請參考前端學習路線圖。

  2. 練習寫程式:通過小型專案或在 LeetCode 和 HackerRank 等平台上解決問題,來提升您的前端寫程式技能。專注於與前端開發相關的挑戰。

  3. 學習現代框架(framework)和函式庫(library):了解如 React、Angular 或 Vue.js 等流行的框架和函式庫。掌握這些工具對於現代前端開發職位至關重要。

  4. 熟悉開發工作流程中的基礎工具:確保您對基本工具和實踐(例如版本控制工具 Git、測試工具(單元測試和整合測試)以及建構工具如 Vite)感到熟悉。這些對於任何前端角色來說都至關重要。

  5. 理解 UI/UX 原則:掌握基本的設計和用戶體驗概念可以讓您在前端開發者中脫穎而出。學習無障礙設計、響應式設計,以及如何創建直觀的界面。

  6. 準備作品集:設計和開發作品集或是 side project 可以幫助你展現你在前端的技術能力。

  7. 研究不同公司和產業:透過了解您面試的公司業務和產品,表現出對公司的興趣。準備一些問題在面試時提出,展示您對該角色的重視。

  8. 提升溝通能力:雖然這並非特定於前端開發,但好的溝通能力可以幫助我們與設計師和 PM 在工作上更容易溝通協調。

前端工程面試問題清單

Beginner Level

1. 什麼是 HTML/CSS 中的 id 和 class 的差別?

id 是用於唯一標識單個 HTML 元素的識別符號。class 則是一個可重複使用的識別符號,可以應用於多個元素。

HTML 中 id 與 class 的區別

  • 當需要通過 CSS 或 JavaScript 操作單個元素時,應該使用 id
  • 當需要操作一組 DOM 元素時,應該使用 class

在 CSS 中:

  • #id 選擇具有該 id 的特定元素。
  • .class 選擇具有該 class 的所有元素。

2. 什麼是 CSS 中的盒模型(Box Model)?

CSS 盒模型描述了在 DOM 中為元素生成的矩形框。盒模型由以下幾層組成:

  1. 內容(Content): 最內層,用於顯示文字和圖片。
  2. 內邊距(Padding): 內容與邊框之間的空間。
  3. 邊框(Border): 包圍元素的外緣,位於內邊距的外側。
  4. 外邊距(Margin): 邊框外部的空間,用於將該元素與其他元素分隔開。

通過分別控制每一層,您可以定義使用者介面中每個元素的外觀。

3. CSS 中 inline、inline-block 和 block 元素的區別

在 CSS 中,inline、inline-block 和 block 元素的區別在於它們在網頁中的呈現方式:

  1. Inline(行內元素):

    • 不具有寬度或高度的屬性。
    • 不會換行,僅佔據其內容所需的寬度。
    • 範例:<span><a>
  2. Inline-block(行內區塊元素):

    • 與 inline 元素類似,不會換行,但允許設定寬度和高度。
    • 範例:<img>
  3. Block(區塊元素):

    • 會從新行開始,默認佔據可用寬度的整行。
    • 可以自訂寬度和高度。
    • 範例:<div><p>

4. HTML 結構中的 SEO 最佳實踐

純 HTML 方面:

  1. 使用語義化的 HTML: 使用 <header><nav><main><article><section><footer> 等標籤,提供有意義的結構。
  2. 正確的標題層級: 正確使用 <h1><h6>,每個頁面僅使用一次 <h1> 作為主標題,其次是 <h2><h3><h6>等。
  3. Meta 標籤: 包含相關的 <meta> 標籤,例如描述(description)、關鍵字(keywords)和視口(viewport),提供網站的元數據。
  4. 圖片的 Alt 屬性: 為圖片添加描述性的 alt 屬性,以提高無障礙性和搜尋引擎的理解能力。

其他最佳實踐:

  1. 內部連結: 使用內部鏈接連接網站內的內容,幫助搜尋引擎爬取並了解網站結構。
  2. 行動裝置友好設計: 以行動優先的方式設計網站和 CSS,確保響應式設計以提升用戶體驗和搜尋引擎排名。
  3. 快速載入時間: 優化圖片、使用高效的代碼並利用快取來提升頁面載入速度,載入速度越快,在搜尋結果中越具競爭力。

5. 什麼是文件物件模型(DOM)?

文件物件模型(Document Object Model,DOM)是用於網頁文件的 API,它將 HTML 網頁的結構表示為一棵樹,其中每個節點對應於文檔中的一部分(例如元素、屬性或文本)。


6. 如何為元素添加事件監聽器?

要為元素添加事件監聽器,首先需要通過文檔物件的某種方法(如 getElementById)獲取該元素,然後使用該物件的 addEventListener 方法。

此方法需要接收三個參數:事件名稱(如 clickkeyupmouseup 等)、事件處理函數,以及可選的 boolean(指示是否在捕獲階段觸發事件)。


7. null 和 undefined 的區別是什麼?

在 JavaScript 中:

  • undefined 是新變數的默認值,表示變數已被定義,但尚未賦值。
  • null 是一個值,表示沒有值沒有物件,需要由開發者明確賦值給變數。

8. cookies、sessionStorage 和 localStorage 的區別是什麼?

  • Cookies:
    小型資料片段,儲存於瀏覽器中,主要用於在 HTTP 請求之間保留資訊,例如用戶身份驗證、會話管理和追蹤用戶行為。

  • sessionStorage:
    用於臨時儲存,只能在同一個 session 中訪問(即瀏覽器視窗或標籤頁開著時)。當瀏覽器視窗關閉時,數據會丟失。

  • localStorage:
    sessionStorage 類似,但資料在瀏覽器窗口或標籤頁關閉後依然存在,提供長期存儲功能。相較於 cookieslocalStorage 的大小限制更大,更適合儲存大資料集。


9. 瀏覽器如何渲染網站?

瀏覽器渲染網頁的過程包括以下幾個步驟:

  1. 解析 HTML。
  2. 解析 CSS 並應用樣式。
  3. 計算頁面佈局中每個元素的位置。
  4. 將實際像素繪製到屏幕上,並將它們排序到不同的圖層中。
  5. 組合所有圖層,根據 z-index 值、不透明度等渲染到屏幕上。
  6. 執行 JavaScript 代碼。
  7. 加載非同步資源。

10. 什麼是媒體查詢(Media Queries)?

媒體查詢是 CSS 中的一項功能,允許前端開發者根據設備或視口的各種特性應用不同的樣式。例如,根據設備的寬度、高度、方向或類型設定不同的樣式。

通過媒體查詢,可以實現響應式設計,使樣式適應不同的屏幕尺寸和設備能力。


中級知識

1. em 和 rem 單位的區別

EM 和 REM 在 CSS 中的比較
它們都是相對單位,但相對於的基準不同:

  • em:相對於父元素的字體大小。如果父元素的字體大小是 20px,設定 font-size: 2em 則等於 40px。
  • rem:相對於頁面根元素(<html> 元素)的字體大小。例如,若 <html> 的字體大小是 16px,則 1rem 等於 16px。

2. 如何建立 Flexbox 布局

建立 Flexbox 布局需要兩個主要步驟:

  1. 設置容器元素:display: flex; 屬性應用到容器元素上。
  2. 設定子元素的 Flex 屬性: 對容器內的每個元素設置 Flexbox 屬性,例如 flex: 1

3. CSS 特殊性(Specificity)的解釋及其運作原理

CSS 特殊性用於決定在樣式衝突時應該應用哪一組樣式。它遵循以下優先順序:

  1. 行內樣式(Inline style): 擁有最高優先權,會覆蓋其他樣式。
  2. ID 選擇器: 僅次於行內樣式,覆蓋其他類型的樣式。
  3. 類別選擇器(Class-based selectors): 覆蓋類型選擇器,但低於 ID 選擇器和行內樣式。
  4. 類型選擇器(Type selectors): 優先級最低,會被其他選擇器覆蓋。

4. 如何建立 CSS Grid 布局

建立 Grid 布局的步驟:

  1. 將包含元素設置為 display: grid
  2. 使用 grid-template-rowsgrid-template-columns 屬性定義網格的結構。
  3. 將元素放置於網格容器內,並使用 grid-columngrid-row 屬性指定位置。

5. 什麼是閉包(Closures),以及如何使用它們?

閉包是指當一個函數定義在另一個函數內時,即使外部函數已執行完畢,內部函數仍然可以訪問外部函數的變數和參數。
閉包的用途:

  • 創建私有變數: 內部函數可以訪問,但外部無法直接存取。
  • 實現複雜對象: 創建只有內部上下文可用的豐富數據結構。

6. 什麼是事件委派(Event Delegation)?

事件委派是一種在父元素上定義事件處理器的技術,用來處理子元素觸發的事件。
事件委派原理:
當事件被觸發時,它會沿著 DOM 層次結構向上冒泡,直到到達父元素的事件處理器。


7. 什麼是 Promise,如何運作?

Promise 是 JavaScript 中用於表示非同步操作最終完成(或失敗)的對象。

  • 用途: 通過 Promise,可以處理非同步操作的成功結果或失敗情況。
  • 工作原理: Promise 提供 .then().catch() 方法,分別用於處理成功和失敗的結果。

8. 如何優化網站資源以加快加載時間?

根據資源類型,使用不同的優化技術:

  • CSS 和 JavaScript 文件: 最小化並壓縮代碼。
  • 圖片: 使用如 JPEGOptim 或 ImageOptim 等工具壓縮圖片,確保過程中不損失質量。

9. 什麼是 Service Workers?它們的用途是什麼?

Service Workers 是在網頁應用程序背景中執行的腳本,與網頁主線程分開運作,提供以下功能:

  • 離線快取。
  • 推送通知。
  • 背景同步。

10. 什麼是同源政策(Same-Origin Policy)?

同源政策是瀏覽器中的一項安全功能,用於防止網站從其他網站訪問數據(如導入腳本或 API 請求)。

  • 用途: 防止惡意腳本竊取其他網站的敏感數據(如 cookies、本地存儲或內容)。
  • 解決方案: 使用跨源資源共享(CORS)。服務器需指定允許訪問的域名,並且客戶端應發送正確的標頭,雙方即可進行互動,即使不在同一域名下。

進階知識

1. 什麼是 CSS 變數?什麼時候會用到?

CSS 變數類似於前端程式語言中的變數,可以由開發者設置並在整個 CSS 樣式表中重複使用。

  • 優點: 將全局使用的值(如顏色)集中管理。例如,CSS 框架常用變數設置常量(如將黑色設置為 #222 而非 #000)。
  • 應用場景: 當網站需要一致性的設計風格或易於更新的全局樣式時。

2. 如何實現 Critical CSS 優化網頁的加載時間?

Critical CSS 是指將關鍵 CSS 規則從 CSS 文件中移除,並內嵌到網站的 <head> 元素中:

  1. 方法: 將頁面渲染所需的關鍵樣式直接嵌入到 HTML 文件的 <head> 中。
  2. 好處: 關鍵樣式能立即加載,減少渲染時間;非關鍵樣式則隨後加載(如主 CSS 文件)。

3. JavaScript 中的事件循環(Event Loop)是如何運作的?

事件循環是 JavaScript 的核心概念,允許執行非同步代碼。
運作過程:

  1. 調用棧(Call Stack): JavaScript 使用單線程執行代碼,函數按順序添加到調用棧,執行完畢後移除。
  2. 非同步操作: 非同步操作由瀏覽器的 Web API 處理,從調用棧中移除,單獨執行。
  3. 任務隊列(Task Queue): 當非同步操作完成後,回調函數被放入任務隊列中等待執行。
  4. 事件循環: 檢查調用棧是否為空,若為空,將任務隊列中的回調函數推入調用棧執行。

4. JavaScript 中的非同步操作處理方式有哪些?

JavaScript 提供了 4 種主要方法處理非同步操作:

  1. 回調函數(Callbacks): 當非同步操作完成後調用指定函數。
  2. Promises: 表示非同步操作最終完成的結果,使用 .then().catch() 處理成功或失敗情況。
  3. Async/Await: Promise 的進化語法,讓非同步代碼看起來像同步代碼,易於閱讀和維護。
  4. 事件監聽器(Event Listeners): 當特定事件(如用戶操作)觸發時調用回調函數。

5. 如何在單頁應用程式中管理狀態?

在沒有使用函式庫或是框架(如 React 或 Vue.js)的情況下,管理狀態相對複雜,可考慮以下方法:

  1. 全局變數: 使用全域變數或全域對象集中管理狀態,但大型應用難以維護。
  2. 模組模式(Module Pattern): 將狀態封裝到模組內,提供清晰的 API 來管理狀態。
  3. 發布/訂閱模式(Pub/Sub Pattern): 基於事件驅動的架構,分離狀態變更邏輯,更靈活但更複雜。
  4. 狀態管理庫: 使用像 Redux 這樣的庫,幫助統一管理應用的狀態。

6. 虛擬 DOM 的運作方式及其優勢

運作方式:

  1. 將用戶界面複製到記憶體中的「虛擬 DOM」,這是一個輕量級的 DOM 副本。
  2. 當狀態變化時,創建新的虛擬 DOM 並與舊版本進行比較(Diff 算法)。
  3. 系統計算出最小的更新操作,僅修改需要變更的節點,減少真實 DOM 操作。

優勢:

  • 性能優化: 通過減少 DOM 更新次數,降低 UI 的重排和重繪成本。
  • 跨平台支持: 虛擬 DOM 提供了應用與渲染 API 的抽象層,支持跨平台實現。
  • 一致性: 確保 UI 與內部狀態同步,減少錯誤和不一致情況。

7. 什麼是伺服器端渲染(SSR)?何時使用?

伺服器端渲染(SSR): 是由伺服器生成完整的 HTML,並將其發送給客戶端,而非在客戶端動態生成內容(即客戶端渲染,CSR)。

適用場景:

  • 內容驅動型網站: 如新聞網站、部落格等需要快速呈現內容的網站。
  • SEO 重視應用: 如果網站依賴於搜索引擎流量,SSR 可以改善 SEO 表現。
  • 漸進式 Web 應用: 需要快速加載的應用可使用 SSR 初始渲染,並在客戶端進行後續交互。

8. 如何分析並改進 Web 應用性能?

需要監控的核心指標包括:

  • 首次內容繪製(FCP): 首次內容呈現所需時間。
  • 最大內容繪製(LCP): 最大內容元素呈現所需時間。
  • 可交互時間(TTI): 網頁完全可交互所需時間。
  • 總阻塞時間(TBT): 主線程被阻塞的總時間。
  • 累積佈局偏移(CLS): 測量視覺穩定性。

9. 什麼是內容安全政策(CSP)?如何提高應用的安全性?

CSP(Content Security Policy): 一種安全標準,用於防止跨站腳本(XSS)和代碼注入攻擊。它通過定義和執行允許的資源來源白名單來運作。

優勢:

  • 增強安全性: 有效防禦 XSS 和數據注入攻擊。
  • 更高控制權: 開發者可細化政策來控制內容來源。
  • 符合規範: 幫助達成 OWASP Top 10 等安全合規要求。

10. 什麼是 Tree Shaking?如何提升 Web 應用性能?

Tree Shaking: 是 JavaScript 模組打包工具(如 Webpack、Vite)用來移除未使用代碼的技術。

優勢:

  • 減少打包大小: 移除無用代碼,減少發送給客戶端的資源量,加速加載。
  • 性能提升: 更小的打包大小使解析和執行代碼更快,提升應用響應速度。
  • 資源最佳化: 開發者可撰寫模組化代碼,而無需擔心未使用的依賴影響打包體積。

參考文件

  1. github developer-roadmap
  2. Top 30 Popular Front End Developer Interview Questions
  3. 2020 資深前端工程師面試心得(4y, 100k+)
  4. 最常見的前端面試題目
  5. 前端工程師面試問題集 - H5BP
  6. 前端工程師面試問題集
  7. 前端面試問題
  8. 前端面試技術考實戰分享-JS 篇
  9. QNAP/雷技/Yahoo/Synology-前端工程師-面試經驗分享
  10. 2024 資深前端工程師面試心得
  11. JavaScript Questions

React Hooks useCallback 入門教學 | w3schools 學習筆記

· 閱讀時間約 3 分鐘
KD Chang
Maker, Curator & Lifelong learner

前言

在 React 中,每次元件重新渲染時,元件內部定義的函式也會被重新建立。這會導致某些子元件即使其 props 沒有變動,卻仍然會被重新渲染,造成效能浪費。useCallback Hook 是 React 提供的工具,用來記憶(memoize)函式的參考,以避免不必要的重新渲染。它的使用情境與 useMemo 類似,不同之處在於:useMemo 記憶的是「值」,而 useCallback 記憶的是「函式」。


重點摘要

  • useCallback 是 React 提供的 Hook,可回傳一個記憶化的函式
  • 記憶化的概念就像是快取(caching),只在依賴值變更時才重新建立函式。
  • useCallback 可改善效能,避免函式在每次重新渲染時被重新定義。
  • 常與 React.memo 搭配使用,防止不必要的子元件重新渲染。
  • useMemo 類似,但 useMemo 回傳的是「值」,useCallback 回傳的是「函式」。
  • 若函式沒有依賴到外部變數,也可以將依賴陣列設為空陣列([]),表示永遠不重新建立。

問題說明:為何子元件會重渲染?

在下列範例中,即使 todos 陣列沒有變動,點擊 + 按鈕後,子元件 Todos 仍會重新渲染:

// index.js
import { useState } from 'react';
import ReactDOM from 'react-dom/client';
import Todos from './Todos';

const App = () => {
const [count, setCount] = useState(0);
const [todos, setTodos] = useState([]);

const increment = () => {
setCount((c) => c + 1);
};

const addTodo = () => {
setTodos((t) => [...t, 'New Todo']);
};

return (
<>
<Todos todos={todos} addTodo={addTodo} />
<hr />
<div>
Count: {count}
<button onClick={increment}>+</button>
</div>
</>
);
};

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
// Todos.js
import { memo } from 'react';

const Todos = ({ todos, addTodo }) => {
console.log('child render');
return (
<>
<h2>My Todos</h2>
{todos.map((todo, index) => (
<p key={index}>{todo}</p>
))}
<button onClick={addTodo}>Add Todo</button>
</>
);
};

export default memo(Todos);

上述例子中,我們使用了 React.memo 包裹 Todos 元件,理論上當 todos 不變時,它不該重新渲染。然而,當你點擊「+」按鈕後 Todos 仍然會重渲染,原因在於:

參考相等性(Referential Equality)

每次 App 元件重新渲染時,addTodo 函式都會重新建立,導致其記憶位置不同。雖然 Todosprops.todos 沒變,但 props.addTodo 是一個新的函式,因此 React.memo 偵測到 props 有變動,就會導致子元件重新渲染。


解法:使用 useCallback 記憶函式

為了解決上述問題,可以使用 useCallback 記憶 addTodo 函式,讓它只在 todos 改變時才重新定義:

// index.js
import { useState, useCallback } from 'react';
import ReactDOM from 'react-dom/client';
import Todos from './Todos';

const App = () => {
const [count, setCount] = useState(0);
const [todos, setTodos] = useState([]);

const increment = () => {
setCount((c) => c + 1);
};

const addTodo = useCallback(() => {
setTodos((t) => [...t, 'New Todo']);
}, [todos]);

return (
<>
<Todos todos={todos} addTodo={addTodo} />
<hr />
<div>
Count: {count}
<button onClick={increment}>+</button>
</div>
</>
);
};

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);

此時,當點擊 + 按鈕時,addTodo 函式不再被重新建立,因此 Todos 元件也不會被不必要地重新渲染。


總結

  • useCallback 是優化 React 應用效能的重要工具,尤其是在元件傳遞函式給子元件時。
  • 若你搭配 React.memo 使用,請務必搭配 useCallback,以避免函式參考不一致而導致的重渲染。
  • 不過也不要過度使用 useCallback,因為它本身也有效能成本,應該根據實際需求與效能瓶頸來使用。

在實務中,對於常被重複渲染、或複雜邏輯包裝的子元件來說,useCallback 能有效避免不必要的重新渲染,是撰寫高效 React 應用程式時不可忽視的工具。

參考文件

  1. React Custom Hooks

10 key terms related to backend engineering

· 閱讀時間約 1 分鐘
KD Chang
Maker, Curator & Lifelong learner

Here are 10 key terms related to backend engineering, covering essential skills, tools, and work methodologies:

  1. Node.js / Python / Java / Ruby / PHP – Common backend programming languages used to build server-side applications.

  2. Database – Includes SQL (e.g., MySQL, PostgreSQL) and NoSQL (e.g., MongoDB, Redis) for data storage, management, and optimization.

  3. REST API / GraphQL – Designing and implementing server-side APIs to support frontend data requests.

  4. Authentication & Authorization – Technologies like OAuth, JWT, and session management to ensure user security and data protection.

  5. Docker / Kubernetes – Containerization and orchestration tools for application deployment and resource management.

  6. Cloud Services – Platforms like AWS, Google Cloud, and Microsoft Azure for cloud-based application hosting and operations.

  7. Microservices – Architectural style for designing modular and independently deployable services.

  8. Message Queues – Tools like RabbitMQ and Kafka for asynchronous communication in distributed systems.

  9. Version Control – Systems like Git/GitHub for code management and team collaboration.

  10. Performance Optimization – Techniques for improving server and database performance to enhance system efficiency.

These key concepts form the foundation of a backend engineer’s work and career growth. By continuously gaining experience and developing projects, one can become a more proficient software engineer.

React Hooks useReducer 入門教學 | w3schools 學習筆記

· 閱讀時間約 3 分鐘
KD Chang
Maker, Curator & Lifelong learner

前言

在 React 中,useState 是管理狀態的基礎 Hook,但當應用的狀態邏輯越來越複雜,像是涉及多個欄位更新、交叉依賴或分支邏輯,使用 useState 可能變得難以維護。這時,React 提供的 useReducer Hook 是一個更合適的選擇。

useReducer 的概念與 Redux 類似,它讓你將狀態管理邏輯集中在一個 reducer 函式中,透過 dispatch 發送動作來更新狀態,使邏輯清晰、可維護性高,非常適合用於中型到大型的應用。


重點摘要

  • useReduceruseState 類似,但更適合處理複雜邏輯或多狀態管理。

  • 語法格式為:const [state, dispatch] = useReducer(reducer, initialState)

  • reducer 是一個函式,根據傳入的 action 物件決定如何更新狀態。

  • 所有狀態更新都透過 dispatch(action) 進行。

  • 初始狀態 initialState 通常是物件或陣列。

  • 適合用在:

    • 表單資料管理
    • Todo List 或清單類資料
    • 複雜的元件邏輯

實際範例:使用 useReducer 建立 Todo 狀態管理

以下範例展示如何透過 useReducer 管理一個 Todo List 的完成狀態切換邏輯。

import { useReducer } from 'react';
import ReactDOM from 'react-dom/client';

// 初始狀態:包含兩個代辦事項
const initialTodos = [
{
id: 1,
title: 'Todo 1',
complete: false,
},
{
id: 2,
title: 'Todo 2',
complete: false,
},
];

// reducer 函式:根據 action 決定如何更新狀態
const reducer = (state, action) => {
switch (action.type) {
case 'COMPLETE':
return state.map((todo) => {
if (todo.id === action.id) {
return { ...todo, complete: !todo.complete }; // 切換完成狀態
} else {
return todo;
}
});
default:
return state; // 沒有匹配的 action 則回傳原狀態
}
};

// 元件:顯示代辦事項清單
function Todos() {
const [todos, dispatch] = useReducer(reducer, initialTodos);

// 處理 checkbox 切換
const handleComplete = (todo) => {
dispatch({ type: 'COMPLETE', id: todo.id });
};

return (
<>
{todos.map((todo) => (
<div key={todo.id}>
<label>
<input type="checkbox" checked={todo.complete} onChange={() => handleComplete(todo)} />
{todo.title}
</label>
</div>
))}
</>
);
}

// 渲染應用
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Todos />);

說明

  • initialTodos 是初始狀態,為一個陣列,包含代辦事項的 idtitlecomplete 狀態。
  • reducer 是一個純函式,根據傳入的 action 型別(此處為 "COMPLETE")來更新對應的 todo 狀態。
  • dispatch 是用來觸發 reducer 的函式,只要呼叫 dispatch({ type: "COMPLETE", id: todo.id }),就會依據 id 切換該 todo 的完成狀態。

延伸說明

上述範例僅實作了完成狀態的切換,但實務中 useReducer 更能發揮作用,因為你可以整合所有的 CRUD 操作邏輯於同一個 reducer 裡,例如:

case "ADD":
return [...state, newTodo];
case "DELETE":
return state.filter(todo => todo.id !== action.id);
case "UPDATE":
return state.map(todo => todo.id === action.id ? { ...todo, title: action.title } : todo);

這樣一來,整個應用的狀態更新都統一由 reducer 管理,讓邏輯集中且更容易除錯與擴充。


總結

useReducer 是 React 提供用來處理複雜狀態邏輯的重要工具。當你遇到以下情況時,建議考慮使用 useReducer

  • 多個狀態變數需要統一處理
  • 狀態轉換邏輯複雜且重複
  • 想將狀態管理從元件中抽離以提升可讀性

透過 useReducer,你可以實現更模組化、可維護的應用狀態邏輯,使開發效率更高、錯誤更少。

參考文件

  1. React Custom Hooks

React Hooks useRef 入門教學 | w3schools 學習筆記

· 閱讀時間約 3 分鐘
KD Chang
Maker, Curator & Lifelong learner

前言

在 React 中,useRef 是一個非常實用的 Hook,它提供了一個方法來在元件重新渲染之間保留值。不同於 useState,當 useRef 的值改變時不會觸發元件重新渲染,這使它成為追蹤狀態變化、儲存 DOM 參考或避免不必要重新渲染的理想選擇。

本文將深入說明 useRef 的三種主要用途:

  1. 避免重新渲染的狀態儲存
  2. 直接操作 DOM 元素
  3. 追蹤先前的狀態值

重點摘要

  • useRef 可用來在元件間保留值而不觸發重新渲染。

  • 常用於:

    • 儲存不可變的值或變數
    • 直接存取 DOM 元素(透過 ref
    • 記錄先前的狀態(例如輸入欄位的歷史值)
  • useRef() 回傳一個包含 .current 屬性的物件。

  • 修改 ref.current 不會導致畫面重繪,因此對效能影響小。


實際範例

範例 1:避免重新渲染的計數器

使用 useRef 追蹤畫面渲染次數(如果用 useState 反而會導致無限循環):

import { useState, useEffect, useRef } from 'react';
import ReactDOM from 'react-dom/client';

function App() {
const [inputValue, setInputValue] = useState('');
const count = useRef(0);

useEffect(() => {
count.current = count.current + 1;
});

return (
<>
<input type="text" value={inputValue} onChange={(e) => setInputValue(e.target.value)} />
<h1>Render Count: {count.current}</h1>
</>
);
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);

說明:

  • useRef(0) 初始化 count.current 為 0。
  • 每次渲染後,useEffect 更新 count.current
  • 即使值變了,畫面不會重新渲染,這正是 useRef 的特性。

範例 2:存取 DOM 元素

在 React 中,大部分的 DOM 操作應交由框架管理,但在特定情況下,我們需要手動聚焦、選取或操作元素,這時 useRef 就派上用場。

import { useRef } from 'react';
import ReactDOM from 'react-dom/client';

function App() {
const inputElement = useRef();

const focusInput = () => {
inputElement.current.focus();
};

return (
<>
<input type="text" ref={inputElement} />
<button onClick={focusInput}>Focus Input</button>
</>
);
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);

說明:

  • 使用 ref={inputElement} 將 input 元素指向 useRef() 的回傳值。
  • 呼叫 inputElement.current.focus() 來讓輸入框聚焦。

範例 3:追蹤先前的狀態值

如果你想知道某個值在上一次渲染的狀態是什麼,可以使用 useRef 搭配 useEffect 來達成。

import { useState, useEffect, useRef } from 'react';
import ReactDOM from 'react-dom/client';

function App() {
const [inputValue, setInputValue] = useState('');
const previousInputValue = useRef('');

useEffect(() => {
previousInputValue.current = inputValue;
}, [inputValue]);

return (
<>
<input type="text" value={inputValue} onChange={(e) => setInputValue(e.target.value)} />
<h2>Current Value: {inputValue}</h2>
<h2>Previous Value: {previousInputValue.current}</h2>
</>
);
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);

說明:

  • useRef 儲存上一個 inputValue
  • 每次 inputValue 改變時,useEffect 都會更新 previousInputValue.current
  • 在畫面上同時顯示目前值與上一個值。

總結

useRef 是 React 中一個簡單卻強大的工具,適用於以下情境:

  • 維持不影響渲染的狀態資料(例如計數器、定時器 ID、外部資料等)
  • 直接操作 DOM 元素(例如設定焦點、自動滾動)
  • 追蹤先前的狀態值或變數(例如表單內容變化)

理解並善用 useRef 可以幫助你更靈活地處理 React 元件中的各種非視覺狀態,讓應用程式更穩定、效能更佳。下次當你不需要觸發重新渲染時,請記得考慮使用 useRef


如果你需要我幫你將此內容轉換為 Markdown、部落格文章格式或簡報稿,也可以告訴我。

參考文件

  1. React Custom Hooks

React Hooks useContext 入門教學 | w3schools 學習筆記

· 閱讀時間約 3 分鐘
KD Chang
Maker, Curator & Lifelong learner

前言

在 React 應用程式中,當需要在多個巢狀元件之間共享資料時,傳遞 props 是最基本的做法。但當元件層級變深,這樣的資料傳遞會變得繁瑣且難以維護,這種情況被稱為「props drilling」。為了解決這個問題,React 提供了 Context API,搭配 useContext Hook 可以讓你在不需要一層層傳遞 props 的情況下,輕鬆地在深層元件中讀取共享的狀態。


重點摘要

  • React Context 是一種全域狀態管理的工具。
  • 可以搭配 useState 使用,實現跨元件樹狀結構的資料共享。
  • 「prop drilling」是指一層層傳遞 props,容易造成程式碼混亂。
  • 使用 Context 包裝需要共享資料的元件樹,可避免不必要的傳遞。
  • useContext Hook 用於在任意元件中讀取指定 Context 的值。

問題背景與傳統寫法

假設我們有一個使用者名稱 user 的狀態,我們希望在最上層的元件設定這個狀態,並在最底層的第 5 個元件中使用它。傳統的做法會是逐層傳遞:

import { useState } from 'react';
import ReactDOM from 'react-dom/client';

function Component1() {
const [user, setUser] = useState('Jesse Hall');

return (
<>
<h1>{`Hello ${user}!`}</h1>
<Component2 user={user} />
</>
);
}

function Component2({ user }) {
return (
<>
<h1>Component 2</h1>
<Component3 user={user} />
</>
);
}

function Component3({ user }) {
return (
<>
<h1>Component 3</h1>
<Component4 user={user} />
</>
);
}

function Component4({ user }) {
return (
<>
<h1>Component 4</h1>
<Component5 user={user} />
</>
);
}

function Component5({ user }) {
return (
<>
<h1>Component 5</h1>
<h2>{`Hello ${user} again!`}</h2>
</>
);
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Component1 />);

雖然只有第 1 和第 5 個元件需要這個資料,但第 2 到第 4 個元件也被迫傳遞 props,造成不必要的耦合。


解決方案:使用 React Context 與 useContext Hook

步驟一:建立 Context

import { createContext } from 'react';

const UserContext = createContext();

這段程式碼建立了一個新的 Context,用來傳遞 user 的狀態。


步驟二:使用 Provider 包住需要資料的元件樹

function Component1() {
const [user, setUser] = useState('Jesse Hall');

return (
<UserContext.Provider value={user}>
<h1>{`Hello ${user}!`}</h1>
<Component2 />
</UserContext.Provider>
);
}

UserContext.Provider 負責提供資料給子元件。所有被包住的子元件都能透過 useContext 取得 user 的值。


步驟三:在需要的元件中使用 useContext 取得資料

import { useContext } from 'react';

function Component5() {
const user = useContext(UserContext);

return (
<>
<h1>Component 5</h1>
<h2>{`Hello ${user} again!`}</h2>
</>
);
}

只需一行即可取得 Context 的值,避免層層傳遞 props


完整範例程式碼

import { useState, createContext, useContext } from 'react';
import ReactDOM from 'react-dom/client';

// 建立 Context
const UserContext = createContext();

function Component1() {
const [user, setUser] = useState('Jesse Hall');

return (
<UserContext.Provider value={user}>
<h1>{`Hello ${user}!`}</h1>
<Component2 />
</UserContext.Provider>
);
}

function Component2() {
return (
<>
<h1>Component 2</h1>
<Component3 />
</>
);
}

function Component3() {
return (
<>
<h1>Component 3</h1>
<Component4 />
</>
);
}

function Component4() {
return (
<>
<h1>Component 4</h1>
<Component5 />
</>
);
}

function Component5() {
const user = useContext(UserContext);

return (
<>
<h1>Component 5</h1>
<h2>{`Hello ${user} again!`}</h2>
</>
);
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Component1 />);

總結

透過 useContext Hook 搭配 Context API,你可以有效管理全域或區域性的狀態,避免繁瑣的 props 傳遞,讓元件間的溝通更加清晰與高效。這種模式特別適用於需要在多個巢狀元件中共享資料的情境,例如主題切換、登入狀態管理、使用者資料等。

熟練掌握 React Context 與 useContext,將大幅提升你在開發大型 React 應用的能力與維護性。

參考文件

  1. React Custom Hooks

React Hooks useEffect 入門教學 | w3schools 學習筆記

· 閱讀時間約 3 分鐘
KD Chang
Maker, Curator & Lifelong learner

前言

在 React 函式型元件中,useEffect 是一個強大且常用的 Hook,用來處理副作用(side effects)。副作用指的是那些不直接涉及元件渲染的操作,例如:發送 API 請求、操作 DOM、設定或清除計時器等。

傳統上,這些操作會在 componentDidMountcomponentDidUpdatecomponentWillUnmount 等生命週期函式中進行,而在函式元件中,useEffect 正是用來統一處理這些行為。


重點摘要

  • useEffect 可以執行副作用操作,例如:抓取資料、設定計時器、監聽事件。
  • 語法格式:useEffect(函式, 依賴陣列)
  • 不提供第二個參數時,useEffect 每次重新渲染都會執行。
  • 傳入空陣列作為第二個參數,則只會在元件初次渲染時執行一次。
  • 若依賴陣列中包含特定的 state 或 props,只要它們改變,副作用就會重新執行。
  • 可以在 useEffect 裡透過回傳一個函式進行資源清除(cleanup),避免記憶體洩漏。

實際範例

範例一:沒有依賴陣列,導致每次渲染都執行

import { useState, useEffect } from 'react';
import ReactDOM from 'react-dom/client';

function Timer() {
const [count, setCount] = useState(0);

useEffect(() => {
setTimeout(() => {
setCount((count) => count + 1);
}, 1000);
});

return <h1>I've rendered {count} times!</h1>;
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Timer />);

問題說明:

  • 每次渲染都會重新執行 useEffect,導致 setTimeout 一直重複,數字不斷累加,非預期行為。

範例二:使用空陣列作為依賴,只執行一次

import { useState, useEffect } from 'react';
import ReactDOM from 'react-dom/client';

function Timer() {
const [count, setCount] = useState(0);

useEffect(() => {
setTimeout(() => {
setCount((count) => count + 1);
}, 1000);
}, []); // 加上空陣列,只在初始渲染時執行一次

return <h1>I've rendered {count} times!</h1>;
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Timer />);

重點:

  • 空陣列表示沒有任何依賴,因此只會在元件掛載時執行一次副作用。

範例三:有依賴變數,根據 count 改變而重新執行

import { useState, useEffect } from 'react';
import ReactDOM from 'react-dom/client';

function Counter() {
const [count, setCount] = useState(0);
const [calculation, setCalculation] = useState(0);

useEffect(() => {
setCalculation(() => count * 2);
}, [count]); // 每次 count 改變就重新計算

return (
<>
<p>Count: {count}</p>
<button onClick={() => setCount((c) => c + 1)}>+</button>
<p>Calculation: {calculation}</p>
</>
);
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Counter />);

重點:

  • 依賴陣列中包含 count,因此只要 count 改變,useEffect 就會重新執行,計算新的值。

副作用的清除(Effect Cleanup)

某些副作用,如計時器、訂閱、事件監聽器等,當元件卸載或依賴改變時,應該清除,否則可能會導致記憶體洩漏或非預期行為。

useEffect 中可以回傳一個函式,用來執行清除動作。

範例四:清除計時器

import { useState, useEffect } from 'react';
import ReactDOM from 'react-dom/client';

function Timer() {
const [count, setCount] = useState(0);

useEffect(() => {
const timer = setTimeout(() => {
setCount((count) => count + 1);
}, 1000);

return () => clearTimeout(timer); // 清除 timeout
}, []);

return <h1>I've rendered {count} times!</h1>;
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Timer />);

說明:

  • setTimeout 被命名為 timer,在 useEffect 的清除函式中使用 clearTimeout(timer) 移除它,避免重複執行。

總結

React 的 useEffect 是處理副作用的主要工具。理解它的運作邏輯、依賴機制與清除策略,能幫助開發者更有效率地控制元件的生命周期與效能。

使用建議:

  • 若副作用只需在元件初次渲染執行,請傳入空陣列。
  • 若需要根據變數變動執行副作用,將其加入依賴陣列中。
  • 若副作用產生了外部資源(如計時器、訂閱等),務必記得清除。

掌握這些原則後,就能更靈活並安全地使用 useEffect,打造高效穩定的 React 應用。

參考文件

  1. React Custom Hooks

React Hooks useState 入門教學 | w3schools 學習筆記

· 閱讀時間約 3 分鐘
KD Chang
Maker, Curator & Lifelong learner

前言

在 React 函式元件中,useState 是最常用的 Hook 之一,能讓我們在無需使用 class 的情況下新增與管理元件的「狀態」(state)。狀態是指會隨著使用者互動或應用邏輯變化而更新的資料,例如:輸入框的內容、按鈕點擊次數、切換的主題顏色等。


重點摘要

  • useState 是 React 提供的 Hook,用來在函式型元件中儲存與更新狀態。
  • 使用前需先從 react 匯入 useState
  • useState(初始值) 會回傳一個陣列,包含目前的狀態值與更新狀態的函式。
  • 更新狀態請使用 setXXX 函式,不可直接修改狀態變數
  • 可以建立多個 useState 追蹤不同變數,也可使用一個物件整合多個欄位。
  • 若要更新物件或陣列的部分內容,應使用展開運算子(spread operator)來保留其他值。

實際範例

1. 匯入 useState

import { useState } from 'react';

使用時,請確認使用的是具名匯入(named import),useStatereact 模組的一部分。


2. 初始化狀態

function FavoriteColor() {
const [color, setColor] = useState('');
}

color 是目前狀態值;setColor 是修改此狀態的函式;初始值設定為空字串。


3. 讀取狀態並渲染

function FavoriteColor() {
const [color, setColor] = useState('red');

return <h1>My favorite color is {color}!</h1>;
}

此例中,畫面上會顯示 My favorite color is red!,透過 JSX 讀取狀態。


4. 使用按鈕更新狀態

function FavoriteColor() {
const [color, setColor] = useState('red');

return (
<>
<h1>My favorite color is {color}!</h1>
<button type="button" onClick={() => setColor('blue')}>
Blue
</button>
</>
);
}

點擊按鈕時,會透過 setColorcolor 更新為 "blue",畫面也會即時更新。


5. 多個狀態變數

function Car() {
const [brand, setBrand] = useState('Ford');
const [model, setModel] = useState('Mustang');
const [year, setYear] = useState('1964');
const [color, setColor] = useState('red');

return (
<>
<h1>My {brand}</h1>
<p>
It is a {color} {model} from {year}.
</p>
</>
);
}

這種方式使用多個 useState 管理多個欄位,彼此獨立。


6. 使用物件作為單一狀態

function Car() {
const [car, setCar] = useState({
brand: 'Ford',
model: 'Mustang',
year: '1964',
color: 'red',
});

return (
<>
<h1>My {car.brand}</h1>
<p>
It is a {car.color} {car.model} from {car.year}.
</p>
</>
);
}

以物件作為狀態,可集中管理多個欄位,也使程式碼更易維護。


7. 更新物件中的單一欄位

function Car() {
const [car, setCar] = useState({
brand: 'Ford',
model: 'Mustang',
year: '1964',
color: 'red',
});

const updateColor = () => {
setCar((prevState) => {
return { ...prevState, color: 'blue' };
});
};

return (
<>
<h1>My {car.brand}</h1>
<p>
It is a {car.color} {car.model} from {car.year}.
</p>
<button type="button" onClick={updateColor}>
Blue
</button>
</>
);
}

這裡使用展開運算子(...prevState)來保留其他屬性,僅更新 color。若直接使用 setCar({ color: "blue" }),會造成其他屬性遺失。


總結

React 的 useState 是建立互動式 UI 的基礎,能讓我們在函式型元件中管理狀態。透過這個 Hook,我們可以:

  • 初始化與讀取狀態值
  • 透過更新函式改變狀態並重新渲染畫面
  • 使用多個 useState 管理多個資料
  • 或整合為一個物件並使用展開運算子更新部分欄位

熟練掌握 useState 是學會 React 開發不可或缺的第一步。建議初學者透過實作各種小範例來加深理解與記憶。

參考文件

  1. React Custom Hooks

React Hooks 入門教學 | w3schools 學習筆記

· 閱讀時間約 2 分鐘
KD Chang
Maker, Curator & Lifelong learner

前言

Hooks 是在 React 16.8 版本中加入的新功能。Hooks 讓你可以在「函式元件(function components)」中使用狀態(state)以及其他 React 功能。因此,自從有了 Hooks 之後,類別元件(class components)通常就不再是必要的了

儘管 Hooks 幾乎取代了類別元件的使用方式,但 React 團隊目前並沒有打算移除 class 元件的支援。


什麼是 Hook?

Hooks 讓我們能夠「掛勾(hook into)」React 的核心功能,例如 狀態管理(state)生命週期方法(lifecycle methods)


範例:

import React, { useState } from 'react';
import ReactDOM from 'react-dom/client';

function FavoriteColor() {
const [color, setColor] = useState('red');

return (
<>
<h1>我最喜歡的顏色是 {color}</h1>
<button type="button" onClick={() => setColor('blue')}>
藍色
</button>
<button type="button" onClick={() => setColor('red')}>
紅色
</button>
<button type="button" onClick={() => setColor('pink')}>
粉紅色
</button>
<button type="button" onClick={() => setColor('green')}>
綠色
</button>
</>
);
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<FavoriteColor />);

在這個範例中,我們透過 useState 這個 Hook 來追蹤應用程式的狀態。

狀態(State):泛指那些會隨著使用者互動或應用邏輯而改變的資料或屬性。

此外,使用 Hook 前必須先從 react 模組中引入對應的函式,例如這裡的 useState


使用 Hook 的三大規則

  1. 只能在 React 的函式元件中呼叫 Hooks
  2. 只能在元件的最上層(Top-level)呼叫,不能寫在 iffor函式中等區塊內
  3. 不能有條件式地呼叫 Hook(例如不能寫在 if 判斷中)

注意:Hooks 無法在 class 類別元件中使用!


自訂 Hook(Custom Hooks)

如果你有一些包含狀態的邏輯(stateful logic),需要在多個元件之間重複使用,這時可以考慮封裝成自訂 Hook,以提升可讀性與重用性。

參考文件

  1. React Custom Hooks

在 React 中使用 Sass 進行樣式設計教學 | w3schools 學習筆記

· 閱讀時間約 3 分鐘
KD Chang
Maker, Curator & Lifelong learner

前言

在現代前端開發中,維護與管理 CSS 樣式是一項重要任務,尤其當應用程式日益龐大、元件複雜時,單純使用原生 CSS 經常會遇到樣式難以重用、命名衝突等問題。這時候,Sass(Syntactically Awesome Stylesheets)這類 CSS 預處理器便顯得格外實用。Sass 提供變數、巢狀語法、Mixin 等強大功能,有助於讓樣式更具模組化與可維護性。

本篇教學將說明如何在 React 專案中使用 Sass,從安裝、建立樣式檔案、到實際在元件中引用,手把手帶你完成設定。


重點摘要

  • Sass 是什麼:一種 CSS 預處理器,可在瀏覽器載入前編譯成標準 CSS。
  • 安裝方法:可透過 npm i sass 安裝 Sass 至 React 專案中。
  • 副檔名:Sass 檔案使用 .scss 副檔名。
  • 支援變數與函數:可使用 $變數@mixin 等進階語法撰寫樣式。
  • 與 React 整合方式:與 CSS 類似,透過 import './樣式.scss' 導入樣式。

Sass 是什麼?

Sass(Syntactically Awesome Stylesheets)是一種 CSS 的擴充語法,稱為 CSS 預處理器(preprocessor)。Sass 檔案會在伺服器端進行編譯,轉換為標準的 CSS,然後再由瀏覽器載入。

與傳統 CSS 相比,Sass 提供多種程式化的功能,包括:

  • 變數(Variables)
  • 巢狀語法(Nesting)
  • 混合(Mixins)
  • 繼承(Inheritance)

這些功能可以大幅簡化樣式維護與邏輯。


如何在 React 中使用 Sass?

若你使用 vite 建立專案,只需要簡單幾步即可在 React 中整合 Sass。

安裝 Sass

打開終端機,並在 React 專案目錄中執行以下指令:

npm i sass

安裝完成後,即可開始在專案中撰寫與導入 .scss 檔案。


建立 Sass 檔案

建立 .scss 檔案的方式與 CSS 相同,唯一差別是副檔名由 .css 改為 .scss

假設建立一個名為 my-sass.scss 的檔案,其內容如下:

// 定義一個變數
$myColor: red;

// 使用變數設定 h1 的文字顏色
h1 {
color: $myColor;
}

這段 Sass 程式碼定義了一個 $myColor 的變數,並將其應用於 h1 標題文字的顏色。


在 React 元件中使用 Sass

要在 React 元件中使用 Sass,只需像導入 CSS 檔案一樣導入 .scss 檔案即可。

以下是一個完整範例:

index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import './my-sass.scss'; // 導入 Sass 樣式檔

// 建立一個簡單的元件
const Header = () => {
return (
<>
<h1>Hello Style!</h1>
<p>Add a little style!.</p>
</>
);
};

// 掛載元件到畫面上
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Header />);

只要這樣導入 Sass 檔案,該樣式就會自動被應用到對應的元素上,與原生 CSS 的使用方式幾乎一致。


延伸學習建議

若你對 Sass 有更進一步的興趣,建議深入學習以下主題:

  • Sass Mixin 的建立與使用
  • 巢狀規則與 BEM 命名法結合技巧
  • Sass 的分割與模組化導入(@import / @use)
  • 與 CSS Modules、Styled-components 等工具的比較與選擇

總結

Sass 為 CSS 帶來更多彈性與可維護性,是開發大型 React 應用時的重要利器。配合 vite 的簡單整合方式,即使是初學者也能快速上手。只需幾步就能建立出具備變數與結構邏輯的樣式系統,為專案帶來更清晰、可維護的樣式架構。

無論是個人 Side Project 或是商業應用,學會 Sass 的使用,將大大提升你在前端開發上的效率與品質。

參考文件

  1. React Custom Hooks
  2. React router 官方網站