跳至主要内容

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

· 閱讀時間約 15 分鐘
kdchang
  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 分鐘
kdchang

前言

在 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 分鐘
kdchang

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 分鐘
kdchang

前言

在 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 分鐘
kdchang

前言

在 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 分鐘
kdchang

前言

在 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 分鐘
kdchang

前言

在 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 分鐘
kdchang

前言

在 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 分鐘
kdchang

前言

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 分鐘
kdchang

前言

在現代前端開發中,維護與管理 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 官方網站