跳至主要内容

Django、Flask、FastAPI 吞吐量比較入門教學筆記 | 學習筆記

· 閱讀時間約 4 分鐘
kdchang

前言

在 Python Web 開發領域,Django、Flask 與 FastAPI 是三個非常熱門且廣泛使用的框架。它們各有特點與適用場景,但在性能表現,特別是吞吐量(throughput)方面,存在一定差異。吞吐量通常指每秒鐘能處理的請求數量,是衡量 Web 框架在高併發環境中效率的重要指標。

本篇筆記將簡要介紹這三個框架的基本架構與設計理念,並透過簡單測試與範例比較其吞吐量,幫助初學者理解如何依需求選擇適合的框架。


重點摘要

  • Django

    • 全功能、重量級框架,內建 ORM、管理後台、驗證系統等
    • 同步同步處理,基於 WSGI,預設不支援非同步(Asynchronous)請求
    • 適合需要完整解決方案的中大型專案
    • 吞吐量相較較低,因同步阻塞限制高併發能力
  • Flask

    • 輕量級框架,核心簡單,擴展彈性高
    • 同樣基於同步 WSGI,預設不支援非同步
    • 適合小型、原型開發及彈性需求較多的專案
    • 吞吐量與 Django 相近,瓶頸多來自同步阻塞與部署環境
  • FastAPI

    • 新興的輕量且高性能框架,採用 ASGI 標準,內建非同步支援
    • 基於 Starlette 與 Pydantic,支援非同步 I/O,大幅提升吞吐量
    • 適合高併發、API 開發需求強烈的專案
    • 吞吐量明顯優於 Django、Flask,適合現代微服務架構
  • 吞吐量測試環境與結果

    • 使用同一台機器與相同測試工具(如 wrkabLocust)對簡單 API 進行測試
    • Django、Flask 在同步阻塞環境吞吐量約數千至上萬請求/秒
    • FastAPI 在非同步環境可突破十萬請求/秒,具備更好擴展性
  • 部署差異

    • Django、Flask 多搭配 Gunicorn(WSGI)同步服務器部署
    • FastAPI 搭配 Uvicorn、Hypercorn(ASGI)非同步服務器,性能最佳化

吞吐量比較實際範例

以下將以簡單「Hello World」API 為例,展示三個框架的基本實作,並提供吞吐量測試方法參考。

1. Django 範例

程式碼(views.py):

from django.http import JsonResponse

def hello(request):
return JsonResponse({"message": "Hello World"})

URL 設定(urls.py):

from django.urls import path
from .views import hello

urlpatterns = [
path('hello/', hello),
]

啟動方式:

python manage.py runserver

(正式部署可用 Gunicorn)

2. Flask 範例

程式碼(app.py):

from flask import Flask, jsonify

app = Flask(__name__)

@app.route('/hello')
def hello():
return jsonify(message="Hello World")

if __name__ == "__main__":
app.run()

啟動方式:

python app.py

(正式部署可用 Gunicorn)

3. FastAPI 範例

程式碼(main.py):

from fastapi import FastAPI

app = FastAPI()

@app.get("/hello")
async def hello():
return {"message": "Hello World"}

啟動方式:

uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4

吞吐量測試方法簡介

使用 Linux 下的 wrk 工具對三個 API 進行測試:

若使用 Mac 作業系統可以安裝:

brew install wrk

下指令測試:

wrk -t4 -c100 -d30s http://localhost:8000/hello
  • -t4:4 個 thread
  • -c100:100 個持續連線
  • -d30s:測試持續 30 秒

測試結果可觀察 Requests/sec 欄位,即為吞吐量。


性能分析與比較

  • Django 在簡單請求下可達數千請求/秒,但因同步處理與較重的框架開銷,無法輕易擴展至高併發環境。
  • Flask 同樣是同步,吞吐量略優於 Django,但主要瓶頸仍在同步阻塞與伺服器資源分配。
  • FastAPI 採用非同步設計,利用 Python 的 async/await 及高效事件迴圈,能在相同硬體資源下達到數倍甚至十倍以上吞吐量,尤其適合 I/O 密集型服務。

總結

選擇適合的 Python Web 框架時,需根據專案規模、功能需求及預期流量做權衡。Django 適合快速搭建大型且功能完整的應用,Flask 適合靈活開發與小型專案,而 FastAPI 則是現代高效能 API 開發的首選。

在吞吐量需求高、需要非同步處理的情境下,FastAPI 明顯優勢突出。未來隨著非同步技術普及,FastAPI 的應用範圍將持續擴大。

參考文件

  1. locust 官方文件

Roo Code 詳細介紹:AI VS Code 編輯器外掛入門教學筆記 | 學習筆記

· 閱讀時間約 4 分鐘
kdchang

前言

Roo Code(原名 Roo Cline)是一款嵌入於 Visual Studio Code 編輯器中的開源 AI 代理,能讀寫檔案、自動執行終端指令、操作瀏覽器,甚至整合多種 LLM 模型。無論是編寫程式、重構、除錯、架構設計,Roo Code 都能協助你提升開發效率,更像是一個智慧且自主的開發團隊成員。


重點摘要

  1. 核心功能與定位

    • 像開發團隊的 AI 助手,能讀寫檔案、執行命令、自動操作終端與瀏覽器
    • 支援自然語言溝通,整合 OpenAI、Gemini、Anthropic、Deepseek 等多種提供者
  2. 多模式運作(Modes)

    • Code Mode:主要用於撰寫與 refactor 程式碼
    • Architect Mode:聚焦系統設計與高階規劃
    • Ask Mode:解答問題、技術查詢
    • Debug Mode:專門診斷並修復錯誤
  3. 工具整合

    • 可撰寫檔案、執行 CLI 指令、操控瀏覽器
    • 支援外部工具擴充(MCP:Model Context Protocol),可接入資料庫、API、指令集
  4. 客製化能力強

    • 自訂指令與 prompt
    • 建立自訂模式,定義專屬角色(如測試工程師、設計師)
  5. 專案記憶與 Context 管理

    • 支援 context condensing 和索引技術,避免長案子失去關聯性

安裝與初步設置

1. 安裝 Roo Code

  • 打開 VS Code,前往 Extensions 搜尋「Roo Code」,點擊安裝

2. 設定 AI 提供者

  • 支援 OpenAI-compliant API,例如 OpenAI 本身、OpenRouter、Anthropic、Gemini 等
  • 透過裡面 Preferences 設定 API Key、Model ID、Mode 對應關係

實作範例(實際使用流程)

範例 A:建立新檔案

  1. 切換到 Code Mode
  2. 輸入:「Create a basic HTML template for a portfolio website」
  3. Roo Code 生成 HTML 檔案並顯示 diff
  4. 審核後確定寫入實際工作區

→ 成功驗證:可透過開啟檔案確認生成內容

範例 B:執行命令(CLI / Build / Test)

  1. 讓 Roo 進入程式模式
  2. 輸入:「Run npm install」
  3. Roo 使用終端指令完成套件安裝,且結果顯示在你的終端

→ 驗證指令正確執行

範例 C:使用 Architect Mode 規劃功能

  1. 切換至 Architect Mode
  2. 提出:「Help me plan data access layer for a todo application」
  3. Roo 回應並建立記錄文件(如 .md 記錄實作計畫),建立指引

→ 這種模式能生成結構化內容並建立 Context 記錄


深入技巧與最佳實踐

  • 啟用 Indexing / Context Condensing:讓 Roo 在大型專案中維持上下文關聯性,有效處理長案不遺漏
  • 自訂模式(Custom Modes):為不同角色(如 QA、設計師)建立專屬對話與指令集
  • MCP 擴充:串接外部系統(如 AWS、Jira),擴展 Roo 能力
  • 權限控制:設定自動執行命令範圍、上下文層級,以符合安全標準
  • 模式搭配 LLM 模型:Schema 例:使用 Deepseek R1 做 code、Gigantic Gemini 做 architect、等

操作範例:結合免費 LLM 模型

假設你想要整合免費模型:

  1. 透過 OpenRouter 啟用 DeepSeek R1、Gemini Flash 模型

  2. 將 model 分配到不同模式:如 Architect 模式使用 Gemini、Code 模式使用 DeepSeek

  3. 透過 Roo 對話測試指令:

    • “Generate React component for ToDo item list.”
    • “Update backend schema to include due date.”

總結

Roo Code 是一款功能強大且高度可客製的 AI 編程代理工具,適合想提升開發效率、探索 AI 開發流程的人使用。核心優勢包括:

  • 多種工作模式切換(Code / Architect / Ask / Debug
  • 可控且安全的檔案與終端操作
  • 擴充能力強,可透過 MCP、Context Condensing、自訂模式延伸功能
  • 支援多種 LLM 提供者,具備彈性與擴充性

由於功能豐富,建議一開始可以先安裝外掛並先從簡單任務使用起:生成範例檔案、執行終端指令,接著再進行構架規劃與系統整合。

參考文件

  1. Roo Code 接入 Claude API 完全指南:无惧官网限制而快速用上
  2. 【Vibe Coding 神器】VS Code 超強 AI 插件 Roo Code 實作教學:Ask 模式分析專案、協調器模式開發全新專案、Debug 模式進行除錯!
  3. Vibe Coding 真的這麼神?上集 來自開發團隊的真實導入經驗 聊聊實踐與成效|#VibeCoding #AI #軟體開發 #開發團隊| JUGG 聊敏捷#22
  4. Vibe Coding 真的這麼神?下集 來自開發團隊的真實導入對談|導入的坑、文化與建議|#VibeCoding #AI #軟體開發 #開發團隊 #AI 導入| JUGG 聊敏捷#23
  5. 70% 問題:關於 AI 輔助開發的真實樣貌

12 Factor App 入門教學:打造現代雲端應用的十二守則教學筆記 | 學習筆記

· 閱讀時間約 4 分鐘
kdchang

12 Factor App 是由 Heroku 團隊提出的一套雲端應用架構設計原則,旨在幫助開發者打造可擴展、可維護、易部署的現代化應用程式。這些準則不限語言或框架,廣泛適用於各種 SaaS 應用、API 服務、微服務架構等場景。

以下是每一個 factor 的說明與實際範例:


一、 Codebase(代碼基底)

一個應用對應一個代碼庫,多個部署環境共用該代碼庫

一個應用程式不應散落在多個 Git 倉庫中,即使部署至多個環境(開發、測試、正式),仍應共用同一代碼庫。

範例:

git@github.com:kdchang/todo-api.git  # 統一代碼庫

若你在 GitHub 上有一個 todo-api 倉庫,開發、測試與生產環境(production、staging、dev)的部署都應來自這個倉庫的不同分支或 Tag。


二、 Dependencies(明確聲明相依套件)

使用明確的套件管理工具來聲明所有相依項目,避免依賴系統層級安裝,例如:使用如 requirements.txt, Pipfile, package.json

範例(Node.js)

# package.json 中聲明所有依賴
{
"dependencies": {
"express": "^4.18.2",
"dotenv": "^16.0.3"
}
}

部署時只需透過 npm install 即可安裝所有套件。

三、 Config(環境設定分離)

環境變數應儲存所有設定資訊,而非寫死在程式碼中,例如:.env

這包含資料庫連線、API 金鑰、第三方服務設定等。

範例:

# .env 檔(不要提交到 Git)
DATABASE_URL=postgres://user:pass@host:5432/dbname
JWT_SECRET=my_secret_token

程式碼中透過 process.env 取用這些變數。


四、 Backing Services(外部資源視為附屬服務)

無論是本地資料庫、第三方 API、AWS S3 等,都視為可替換的附屬資源

切換服務供應商不應需改動應用邏輯,只要變更設定即可。例如:DATABASE_URL

範例:

const s3 = new AWS.S3({
accessKeyId: process.env.AWS_ACCESS_KEY,
secretAccessKey: process.env.AWS_SECRET_KEY,
region: process.env.AWS_REGION,
});

五、 Build, Release, Run(建置、發布、執行分離)

明確區分建置(build)、發布(release)與執行(run)三個階段

  • Build:編譯程式、安裝依賴(例如:container)
  • Release:結合建置結果與設定,生成可部署版本。將 build 結果與設定綁定
  • Run:實際執行應用(以 immutable 的方式)

範例(Heroku):

git push heroku main  # 觸發 build 與 release
heroku run npm start # 執行

六、 Stateless Processes(無狀態的執行單元)

應用程式應以一個或多個無狀態進程執行,狀態需存於外部服務

避免將使用者 session 存在記憶體中,應使用 Redis、資料庫等外部服務。可以隨時 scale out

範例(Express):

// Session 儲存至 Redis,而非記憶體
app.use(
session({
store: new RedisStore({ client: redisClient }),
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: true,
})
);

七、 Port Binding(綁定至 Port 提供服務)

應用應自行綁定 port 來對外提供 HTTP 服務,而非依賴外部 Web Server,例如:Gunicorn

這使得應用本身即是一個完整的服務,容易容器化部署。

範例(Node.js):

app.listen(process.env.PORT || 3000, () => {
console.log("Server started");
});

八、 Concurrency(使用程序模型提升並行能力)

透過分工的進程來擴展應用功能,例如 Web、Worker、queue 等

每個類型的處理單位可根據需求水平擴充。

範例(使用 PM2)

pm2 start app.js -i max       # 啟動多個 Web 處理進程
pm2 start worker.js --name worker # 啟動背景任務處理器

九、 Disposability(快速啟動與優雅關閉)

應用應能快速啟動與安全關閉,適應雲端平台的彈性調度

優雅關閉能確保未完成的請求被妥善處理完畢,當使用容器應能快速重啟。

範例(Node.js):

process.on("SIGTERM", () => {
server.close(() => {
console.log("Server closed");
process.exit(0);
});
});

十、 Dev/Prod Parity(開發與生產環境的一致性)

開發、測試、生產環境盡可能相似,降低部署錯誤風險

推薦使用 Docker 來統一環境。盡量減少「只有 production 才會發生」的 bug。

範例:

FROM node:18
WORKDIR /app
COPY . .
RUN npm install
CMD ["npm", "start"]

十一、 Logs(將 log 作為事件串流)

應用不應自行管理 log 文件,而是將 log 輸出到 stdout/stderr,再由平台集中收集與分析

範例:

console.log("User login success", { userId: 123 });
console.error("Database connection failed", error);

在 Heroku、GCP、Kubernetes 等平台會自動收集這些 log,讓 log 管理交給專門工具收集(如 ELK, CloudWatch)


十二、 Admin Processes(一時性管理指令)

資料庫 migration、資料修復等管理任務應能以一次性指令執行

這些指令應與主應用共用相同的環境設定與程式碼。管理性任務(如資料遷移)應獨立於應用程式主進程(例如:python manage.py migrate

範例:

# Sequelize migration 指令
npx sequelize-cli db:migrate

總結

12 Factor App 並不是一套硬性規定,而是建立雲端應用的實務指南。當你的應用朝微服務、CI/CD、雲端部署發展時,這十二項原則能幫助你打造更穩定、可擴充的系統架構。

EventSource API in JavaScript 入門教學筆記 | 學習筆記

· 閱讀時間約 4 分鐘
kdchang

前言

在現代的網頁應用程式中,實時性資料更新是一個常見需求,例如即時通知、股價更新、聊天室訊息、伺服器狀態監控等。傳統上,開發者可能會透過輪詢(Polling)或 WebSocket 來實現。然而,若只是單向由伺服器推送訊息到瀏覽器端,其實有更簡單且高效的選擇:EventSource API

EventSource 基於 Server-Sent Events(SSE),由伺服器主動推送文字資料到客戶端,並且使用 HTTP 協議的持久連線,開發上比 WebSocket 更簡單,適合事件流的場景。本文將帶我們快速入門 EventSource API,理解它的特性與應用方式。


重點摘要

  1. EventSource 與 SSE 的核心概念

    • EventSource 是瀏覽器提供的 JavaScript API,用於接收伺服器推送的 SSE(Server-Sent Events)。
    • 採用 HTTP 長連線,不需要 WebSocket,也不需要額外協議。
    • 支援自動重連(瀏覽器會自動在連線中斷時重新連接伺服器)。
  2. EventSource 的適用場景

    • 即時通知(系統提醒、訊息推送)
    • 資料更新(股價、天氣、賽事比分)
    • 記錄串流(伺服器日誌、事件追蹤)
    • 聊天室訊息(單向推送)
  3. EventSource 的特性

    • 單向通訊:伺服器 → 客戶端
    • 自動重連機制(可透過伺服器端 retry: 指令調整重試時間)
    • 基於純文字的事件格式(MIME type 為 text/event-stream
    • 可透過自訂事件名稱分發不同事件
  4. 與其他技術比較

    • Polling:需要客戶端頻繁請求,耗費頻寬與伺服器資源。
    • WebSocket:雙向溝通更靈活,但需要額外處理協議與狀態管理。
    • EventSource(SSE):單向推送即可,實作簡單、輕量化,適合多數即時通知場景。

EventSource 使用範例

1. 瀏覽器端(JavaScript)

// 建立 EventSource 連線
const eventSource = new EventSource('/events');

// 接收預設訊息(message 事件)
eventSource.onmessage = function (event) {
console.log('收到訊息:', event.data);
};

// 接收自訂事件
eventSource.addEventListener('news', function (event) {
console.log('收到新聞事件:', event.data);
});

// 監控錯誤與連線狀態
eventSource.onerror = function (error) {
console.error('EventSource 發生錯誤:', error);
};

2. 伺服器端(Node.js Express 範例)

const express = require('express');
const app = express();

app.get('/events', (req, res) => {
// 設定 SSE 必要的 Header
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');

// 每隔 3 秒推送一個訊息
const intervalId = setInterval(() => {
const data = new Date().toLocaleTimeString();
res.write(`data: 現在時間 ${data}\n\n`);
}, 3000);

// 當客戶端中斷連線時清理資源
req.on('close', () => {
clearInterval(intervalId);
});
});

app.listen(3000, () => {
console.log('SSE 伺服器運行於 http://localhost:3000/events');
});

3. SSE 資料格式範例

伺服器回傳的資料需符合 text/event-stream 格式,每筆訊息以兩個換行結尾。

data: 這是一個預設訊息

event: news
data: 這是一個新聞更新

event: alert
data: 系統警告訊息
retry: 5000

說明:

  • data::主要訊息內容,可以有多行。
  • event::指定自訂事件名稱,客戶端可用 addEventListener 監聽。
  • retry::定義自動重連的延遲時間(毫秒)。

注意事項

  1. 瀏覽器支援性:大部分現代瀏覽器支援 EventSource(IE 除外)。
  2. 跨域問題:若伺服器與前端不同網域,需設定 CORS。
  3. 資料格式:僅支援 UTF-8 文字資料,若要傳送二進位資料需轉成 Base64 或 JSON。
  4. 連線數限制:部分瀏覽器對同一網域的 SSE 連線數有限制(通常 6 條)。
  5. 斷線重連:內建自動重連機制,但若伺服器返回錯誤狀態碼,可能需手動處理。

總結

EventSource API 提供了一個簡單又高效的方式,讓前端應用程式能夠輕鬆接收伺服器的即時推送。相較於 WebSocket,EventSource 不需要額外的協議處理,也避免了頻繁輪詢帶來的效能浪費。在僅需單向資料更新的場景下,它是一個理想解決方案。

當我們下次需要在前端實現即時通知、動態更新數據或流式資料顯示時,不妨先考慮 EventSource,它或許就是最輕量的選擇。

Server-Sent Events(SSE)入門教學筆記 | 學習筆記

· 閱讀時間約 4 分鐘
kdchang

前言

在現代 Web 應用中,即時資料傳輸已經成為重要需求,例如股票行情更新、聊天室訊息、即時通知等。傳統的 HTTP 請求模式是 客戶端發起請求,伺服器回應一次,這種「單向請求-回應」模式無法有效支持持續更新。

WebSocket 是一個廣為人知的解決方案,提供全雙工通道,但對於單向更新(伺服器推送到客戶端)而言,WebSocket 可能略顯複雜。這時,Server-Sent Events(SSE) 就是一個簡單、輕量級的替代方案。SSE 提供 伺服器到客戶端的單向持續資料推送,使用原生 HTML5 技術即可實現,不需額外協議或第三方套件。


SSE 的核心概念

Server-Sent Events 是基於 HTTP 協議,透過 text/event-stream 的 MIME 類型,保持一個持久連接,讓伺服器可以不斷地向客戶端傳送資料。SSE 的主要特性包括:

  1. 單向通訊

    • 伺服器向客戶端推送資料,客戶端只能接收。若需要雙向通訊,仍需搭配 WebSocket 或 AJAX 請求。
  2. 自動重連

    • 客戶端若因網路斷線或其他原因中斷,瀏覽器會自動重連,可透過 retry 指定重連間隔。
  3. 文字格式傳輸

    • SSE 傳輸的資料以純文字格式為主,每個事件用特定的格式標記(如 event:data:id: 等)。
  4. 支援事件命名

    • 可以自定義事件名稱,在前端監聽不同事件,提高可讀性與維護性。
  5. 輕量且易實作

    • 客戶端只需使用 JavaScript 的 EventSource API,即可快速建立 SSE 連接。

重點摘要

  • 單向連接:伺服器 → 客戶端

  • 持久連接:不需每次重新建立 HTTP 請求

  • 事件格式event, data, id, retry

  • 自動重連:瀏覽器自動處理連線中斷

  • 瀏覽器支援:大部分現代瀏覽器原生支援(IE 除外)

  • 適用場景

    • 即時通知系統
    • 資料更新流(股票、天氣、社群消息)
    • 運行狀態監控儀表板

SSE 的資料格式

SSE 的資料格式遵循 行為約定

id: 1
event: message
retry: 3000
data: Hello, this is a message from server

說明:

  • id(選填):事件 ID,客戶端斷線後重連,會告訴伺服器最後接收的 ID
  • event(選填):事件名稱,自訂事件類型
  • retry(選填):重連間隔,單位為毫秒
  • data(必填):事件資料內容,可有多行

注意:每個事件以空行結束,代表事件結束。


SSE 實作範例

以下示範如何使用 Node.js + Express 建立 SSE 服務,並在前端接收事件:

1. 後端 (Node.js + Express)

const express = require('express');
const app = express();

app.get('/events', (req, res) => {
// 設定 SSE 標頭
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');

let counter = 0;

// 每秒推送一次訊息
const interval = setInterval(() => {
counter++;
res.write(`id: ${counter}\n`);
res.write(`event: message\n`);
res.write(`data: {"count": ${counter}, "time": "${new Date().toISOString()}"}\n\n`);
}, 1000);

// 當客戶端斷線時清理 interval
req.on('close', () => {
clearInterval(interval);
});
});

app.listen(3000, () => {
console.log('SSE server running on http://localhost:3000');
});

2. 前端 (HTML + JavaScript)

<!DOCTYPE html>
<html>
<head>
<title>SSE Example</title>
</head>
<body>
<h1>Server-Sent Events Demo</h1>
<div id="messages"></div>

<script>
const eventSource = new EventSource('http://localhost:3000/events');

eventSource.onmessage = function (event) {
const msgDiv = document.getElementById('messages');
const data = JSON.parse(event.data);
msgDiv.innerHTML += `<p>Count: ${data.count}, Time: ${data.time}</p>`;
};

eventSource.addEventListener('message', function (event) {
// 也可透過自訂事件名稱接收
console.log('Received event:', event.data);
});

eventSource.onerror = function (err) {
console.error('EventSource failed:', err);
};
</script>
</body>
</html>

解釋

  • EventSource 會自動建立持久連接
  • 每次後端 res.write 新事件時,前端 onmessage 會被觸發
  • 客戶端不需發起額外請求,伺服器可以持續推送

SSE 與 WebSocket 的比較

特性SSEWebSocket
通訊方向伺服器 → 客戶端(單向)雙向(伺服器 ↔ 客戶端)
協議HTTPTCP / WebSocket protocol
連接維護自動重連需手動處理斷線
輕量實作非常簡單相對複雜
適用場景事件推送、資料流即時遊戲、聊天室、雙向互動

實務應用建議

  1. 適用場景:單向更新、事件通知、儀表板數據刷新

  2. 不適用場景:需要大量雙向互動、低延遲遊戲或高頻交易

  3. 瀏覽器支援:大部分現代瀏覽器支援,但 IE 需 polyfill

  4. 部署注意

    • 長連接可能對 Nginx / Proxy 設定有影響,需允許長時間 HTTP keep-alive
    • 伺服器端要注意資源釋放,避免記憶體泄漏

結論

Server-Sent Events 是一種 簡單、輕量、易於實作的即時資料推送方案,特別適合需要 單向事件更新 的 Web 應用。對於需要雙向通訊的場景,WebSocket 或其他方案可能更合適。掌握 SSE 可以讓我們在開發即時應用、監控儀表板或通知系統時快速上手,並結合現代瀏覽器的原生支援,提供穩定的即時體驗。

前端 i18n 入門教學與注意事項整理筆記 | 學習筆記

· 閱讀時間約 3 分鐘
kdchang

前言

在現今的全球化應用中,網站或產品若希望觸及更多用戶,提供多語系支援幾乎是必須的功能。這就是所謂的國際化(Internationalization,簡稱 i18n),意即在程式設計階段預先做好結構設計,使系統能根據不同語言與地區的需求,自動載入對應的文案、格式與顯示方式。

本篇筆記將說明前端 i18n 的核心觀念、開發時常見的注意事項,以及如何透過實際程式碼實作一個簡單的多語系功能,協助你快速掌握前端 i18n 的基本功。


重點摘要:i18n 實作注意事項

  1. 避免硬編碼文字:所有顯示文字應抽離為 key-value 翻譯檔,便於日後維護與翻譯。
  2. 使用成熟 i18n 套件:例如 React 的 react-i18next、Vue 的 vue-i18n
  3. 結構化管理翻譯檔案:依功能模組分類翻譯內容,避免 key 混亂或重複。
  4. 支援變數插值與格式化:例如姓名、時間、數字等內容應透過參數傳遞給翻譯函數。
  5. 避免字串拼接組合句子:不同語言語序不同,拼接容易導致語意錯誤。
  6. 設計 UI 時預留文字空間:不同語言的字串長度可能差異很大。
  7. 處理 RTL 語言與排版:如阿拉伯語需設定 direction: rtl,必要時翻轉 UI。
  8. 提供語系切換機制與偵測:可從 navigator.language、URL、cookie 判斷語系。
  9. 設計 fallback 機制:若某語系未翻譯的 key,應自動 fallback 至預設語系。
  10. 翻譯流程建議自動化與工具化:搭配翻譯平台(如 Lokalise、Crowdin)管理翻譯流程與品質。

實作範例:使用 React + react-i18next 實現簡單的 i18n 功能

假設我們有一個需要支援中英文切換的 React 專案,以下將一步步實作基本功能。

步驟一:安裝相關套件

npm install i18next react-i18next i18next-browser-languagedetector

步驟二:建立翻譯檔案(放在 src/locales/

src/locales/en/translation.json

{
"greeting": "Hello, {{name}}!",
"home": {
"title": "Welcome to the homepage"
}
}

src/locales/zh/translation.json

{
"greeting": "哈囉,{{name}}!",
"home": {
"title": "歡迎來到首頁"
}
}

步驟三:初始化 i18n 設定(src/i18n.js)

import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';

import en from './locales/en/translation.json';
import zh from './locales/zh/translation.json';

i18n
.use(LanguageDetector)
.use(initReactI18next)
.init({
resources: {
en: { translation: en },
zh: { translation: zh },
},
fallbackLng: 'en',
interpolation: {
escapeValue: false,
},
});

export default i18n;

步驟四:在應用入口引入 i18n 設定(例如 index.js)

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import './i18n';

ReactDOM.render(<App />, document.getElementById('root'));

步驟五:在元件中使用翻譯

import React from 'react';
import { useTranslation } from 'react-i18next';

function HomePage() {
const { t, i18n } = useTranslation();

const changeLanguage = (lang) => {
i18n.changeLanguage(lang);
};

return (
<div>
<h1>{t('home.title')}</h1>
<p>{t('greeting', { name: 'KD' })}</p>
<button onClick={() => changeLanguage('en')}>English</button>
<button onClick={() => changeLanguage('zh')}>中文</button>
</div>
);
}

export default HomePage;

預期畫面輸出

使用者進入頁面,根據瀏覽器語言自動載入對應語系,或透過按鈕切換語言:

歡迎來到首頁
哈囉,KD!
[English] [中文]

總結

i18n 是每個想要「走向國際」的產品所必備的基礎建設之一。透過妥善設計的翻譯架構與工具整合,不僅能提升使用者體驗,也有助於日後擴展新市場與新語系。

建議開發者在專案初期就規劃好 i18n 架構,並搭配良好的團隊流程與翻譯管理工具,將繁瑣的翻譯作業系統化,避免日後重構的成本。

微前端(Micro-Frontend)介紹與入門教學筆記 | 學習筆記

· 閱讀時間約 4 分鐘
kdchang

前言

隨著前端應用日益複雜、團隊規模擴大,「前端單體應用」(Monolithic Frontend)逐漸面臨維護困難、部署不靈活、開發效率低落等問題。微前端(Micro-Frontend)是一種將大型前端應用拆解為數個獨立子應用的架構設計理念,靈感來自後端的微服務(Microservices)架構。每個子應用可以由不同的團隊獨立開發、部署、維護,並共同組成一個整體的產品。

微前端不是某個框架,而是一種架構模式。它的目標是促進前端大型專案的模組化、團隊分工清晰、技術選型彈性,進而提升整體開發與交付效率。


重點摘要

微前端的常見做法

  1. Module Federation(Webpack 5 原生支援,超常見)
  • 各子應用直接共享模組,不用重複打包
  • Webpack 官方強項,Vite 沒有原生支援
  1. iframe / Web Component(框架無關,通用做法)
  • 每個子應用獨立部署,用 iframe 或 custom elements 包裝
  • Vite/React/Vue/Angular 都可以做
  1. 乾淨的 build output + 部署整合
  • 子應用都 build 出靜態資源,整合到主應用路由

  • 跟工具無關,Vite 也能勝任

  • 定義:微前端是一種將前端應用拆解為多個獨立子應用的架構設計模式。

  • 目的

    • 支援大型團隊並行開發
    • 提高部署彈性(單一子應用可獨立上線)
    • 增加技術選擇自由度(不同子應用可使用不同框架)
  • 核心概念

    • 子應用獨立開發、測試與部署
    • 主應用統一載入與整合子應用
    • 子應用可共享部分資源(如登入狀態、UI 元件)
  • 常見實作方式

    • iframe(早期簡單做法,但 UX 不佳)
    • Web Component(標準化但整合與溝通略複雜)
    • JavaScript 插入與渲染(如 single-spa、Module Federation)
  • 適用時機

    • 專案規模大、開發團隊超過 2 組以上
    • 需要支援異步部署與灰階上線
    • 跨框架共存需求(如同時有 React 與 Vue)

微前端實作方式簡介

1. iframe(不推薦)

將子應用放入 iframe 中載入,雖然簡單,但隔離性太強(無法共用狀態、樣式),SEO 和體驗差,不推薦用於現代 Web 專案。

2. Web Components(中立)

透過瀏覽器原生的 Custom Elements 技術(如 my-app-widget),讓子應用變成一個可重用的 HTML 元件,框架中立,但整合難度高。

3. JavaScript 插入與路由分流(主流)

由主應用動態載入子應用(HTML、JS、CSS),並透過路由或 DOM 控制子應用顯示。可使用像是:

其中 qiankun 是阿里開源的基於 single-spa 的微前端框架,中文文件齊全且上手容易。


實際範例:使用 qiankun 快速建立微前端架構

範例說明

目標:建立一個主應用(main-app),載入兩個子應用(react-app、vue-app)

1. 安裝 qiankun(主應用)

npm install qiankun

2. 主應用主體程式碼(main-app/src/main.ts)

import { registerMicroApps, start } from 'qiankun';

registerMicroApps([
{
name: 'react-app',
entry: '//localhost:3001',
container: '#subapp-container',
activeRule: '/react',
},
{
name: 'vue-app',
entry: '//localhost:3002',
container: '#subapp-container',
activeRule: '/vue',
},
]);

start();

3. 主應用 HTML 模板

<div id="subapp-container"></div>

4. 子應用需支援 qiankun 的生命週期函式(以 React 為例)

export async function bootstrap() {
console.log('React app bootstraped');
}
export async function mount(props) {
ReactDOM.render(<App />, document.getElementById('root'));
}
export async function unmount() {
ReactDOM.unmountComponentAtNode(document.getElementById('root'));
}

5. 子應用 Webpack 設定(publicPath)

output: {
publicPath: 'http://localhost:3001/',
},

微前端的挑戰與注意事項

  • 樣式隔離:CSS 必須避免衝突,可搭配 CSS Modules、Scoped CSS。
  • 狀態共享:登入資訊、使用者資料等需透過 global event 或共享 storage 處理。
  • 路由協調:子應用與主應用須協調 route 設計,避免相互干擾。
  • 部署整合:CI/CD pipeline 需考慮子應用與主應用的獨立部署與測試。

總結

微前端是一種極具彈性的架構設計理念,適合中大型團隊協作、複雜前端系統的模組化開發。不過它也帶來額外的技術成本與整合挑戰。在決定導入微前端前,應評估專案規模、開發團隊結構與維運資源是否適合。

實作上,建議可從單一框架開始(如 React + qiankun),逐步拆分模組與部署機制,再逐步進化為多框架混合的微前端架構,避免過早複雜化系統。

React 效能優化入門教學筆記 | 學習筆記

· 閱讀時間約 6 分鐘
kdchang

前言

隨著前端應用日益龐大,單頁應用(SPA)在初次載入時常面臨 JavaScript 檔案過大、載入時間過久的問題,導致使用者等待時間過長、效能下降。為了解決這個問題,React 與現代建構工具(如 Webpack、Vite)提供了 Code Splitting(程式碼分割)與 Lazy Loading(延遲載入)兩種策略,協助開發者更有效地管理與優化應用程式的載入流程。

程式碼分割(Code Splitting)和惰性載入(Lazy Loading)都是用來優化網頁效能的方法,它們都旨在減少初始加載時間,但實現方式和目標略有不同。 程式碼分割是將程式碼分割成多個較小的塊,而惰性載入則是在需要時才加載這些塊。

一、程式碼分割(Code Splitting)

概念:

程式碼分割是將一個大型的 JavaScript 應用程式分割成多個較小的、獨立的塊,每個塊包含應用程式的一部分程式碼。 這些塊通常是根據路由、元件或功能來分割的。

目標:

主要目標是減少應用程式的初始加載時間,通過只加載使用者當前需要的程式碼塊,而不是一次性加載所有程式碼。

實現方式:

程式碼分割通常使用打包工具(如 Webpack、Rollup 等)和動態import()語法來實現。

使用時機:

在編譯時(build time)就進行分割。

二、惰性載入(Lazy Loading)

概念:

惰性載入是指在需要的時候才加載程式碼,而不是在應用程式初始化時就加載所有程式碼。

目標:

減少應用程式的初始加載時間,特別是對於大型應用程式或元件。

實現方式:

惰性載入通常使用 React.lazy 和 Suspense 元件來實現,也可以配合程式碼分割一起使用。

使用時機:

在執行時(runtime)才加載,通常是當使用者訪問某個路由、觸發某個事件或需要顯示某個元件時。

差異總結

特性程式碼分割 Code Splitting惰性載入 Lazy Loading
概念將程式碼分割成多個塊在需要時才加載程式碼
目標減少初始加載時間,優化效能減少初始加載時間,優化效能
實現方式打包工具,dynamic import()React.lazy, Suspense, dynamic import()
時機編譯時執行時
關聯性程式碼分割是惰性載入的基礎,惰性載入可以利用程式碼分割的結果。

重點摘要

  • Code Splitting(程式碼分割)

    • 是一種將整個應用程式切割成多個檔案的技術
    • 通常由 Webpack、Rollup 等建構工具自動處理
    • 可應用於 route-based 分割、component-based 分割等情境
    • 不代表一定是延遲載入,僅是結構上的切割
  • Lazy Loading(延遲載入)

    • 是一種執行時載入程式碼的策略
    • 常與 import() 搭配,直到實際使用時才載入
    • 通常透過 React.lazySuspense 實現元件的懶載入
    • 是 Code Splitting 的使用方式之一
  • 兩者關係

    • Code Splitting 是靜態建構階段的優化策略
    • Lazy Loading 是執行階段的載入行為
    • Lazy Loading 必須建立在已做 Code Splitting 的前提上
  • 效益

    • 減少主程式 bundle 的大小
    • 提升首次載入速度(First Contentful Paint)
    • 延遲不必要的資源載入,節省頻寬與記憶體

實際範例

範例一:傳統未分割的情況(單一 bundle)

// App.js
import HomePage from './HomePage';
import Dashboard from './Dashboard';

function App() {
return (
<>
<HomePage />
<Dashboard />
</>
);
}

這樣寫會導致 HomePage 和 Dashboard 在應用一開始就被載入,無論使用者有沒有看到這些元件。


範例二:使用 React.lazy 實現 Lazy Loading 與 Code Splitting

// App.js
import React, { Suspense } from 'react';

// Lazy Loading:只有在渲染時才動態 import
const HomePage = React.lazy(() => import('./HomePage'));
const Dashboard = React.lazy(() => import('./Dashboard'));

function App() {
return (
<Suspense fallback={<div>載入中...</div>}>
<HomePage />
<Dashboard />
</Suspense>
);
}

使用 React.lazy() 搭配 import() 會讓 Webpack 將這些元件建立為獨立的 chunk。 真正渲染時(如使用者切換頁面),才會觸發載入行為,減少初始 bundle 體積。


範例三:Route-based Code Splitting(React Router)

// AppRouter.js
import React, { Suspense, lazy } from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';

const Home = lazy(() => import('./pages/Home'));
const Dashboard = lazy(() => import('./pages/Dashboard'));

export default function AppRouter() {
return (
<Router>
<Suspense fallback={<div>頁面載入中...</div>}>
<Switch>
<Route path="/home" component={Home} />
<Route path="/dashboard" component={Dashboard} />
</Switch>
</Suspense>
</Router>
);
}

使用路由為單位切割頁面元件,是最常見的 Code Splitting 實務做法。


範例四:動態 import 實作非元件的延遲載入

// utils.js
export function heavyCalculation(input) {
// 假設這段計算非常耗時
return input ** 10;
}
// App.js
function handleClick() {
import('./utils').then(({ heavyCalculation }) => {
const result = heavyCalculation(5);
console.log(result);
});
}

在某些不需要立即執行的邏輯或大型工具函式庫,也可以透過 import() 動態載入來延遲其成本。


常見問題與補充

Q1:Code Splitting 是自動的嗎?

  • 大部分情況下需要手動設計入口點(如 React.lazyimport()),Webpack 才會建立分離的 chunk。

Q2:只有使用 React.lazy 才能 Lazy Load 嗎?

  • 不一定,import() 是底層機制,也可配合其他框架(Vue、Svelte)或工具(React Loadable)使用。

Q3:懶載入的元件可以預載嗎?

  • 可以,透過 import().then() 觸發一次即可放進瀏覽器快取,達到「預熱」效果。

總結

在前端應用越來越大型化的今天,掌握 Code Splitting 與 Lazy Loading 的差異與使用場景,已成為每位前端工程師的必備技能。Code Splitting 解決的是「結構上的模組分離」,Lazy Loading 則是「載入時機的延後」。兩者密不可分,但用法與思考層次不同。

實務上可先針對頁面級路由進行分割,再進一步優化元件級的載入、工具模組載入時機,逐步降低初始 bundle 體積,提升網站效能與使用者體驗。

參考文件

  1. React | 為太龐大的程式碼做 Lazy Loading 和 Code Splitting

React 效能優化入門教學筆記 | 學習筆記

· 閱讀時間約 4 分鐘
kdchang

前言

React 作為現代前端開發的主流函式庫之一,強調 UI 的組件化與狀態驅動式渲染。然而,隨著應用規模擴大與資料變得動態頻繁,React 應用可能出現重新渲染過多、載入過慢或記憶體占用過高等問題,影響使用者體驗與開發效率。為此,瞭解與掌握 React 的效能優化技巧,成為中高階開發者的重要功課。

本篇筆記將介紹 React 效能優化的核心原則與常見實作方式,搭配簡單的程式碼範例說明實際操作,協助你建立清晰的優化思維與實作經驗。


重點摘要

  • 避免不必要的重新渲染

    • 使用 React.memo 包裹純函式組件
    • 適當使用 useMemouseCallback 記憶運算結果或函式引用
  • Lazy loading(Code Splitting)

    • 使用 React.lazySuspense 實現組件按需載入
  • 列表渲染優化

    • 提供穩定的 key,避免 diff 錯誤導致重繪
    • 處理大量資料時可結合虛擬化工具(如 react-window
  • 狀態管理與邏輯分離

    • 將全域狀態與 UI 狀態分離,減少重渲染範圍
    • 減少 props 傳遞鏈,避免深層組件無謂更新
  • 避免 inline 宣告與函式

    • 每次 render 都會產生新函式或物件,導致子組件重新渲染
  • 效能分析與工具

    • 使用 React DevTools 的 Profiler 模組分析 render 開銷
    • 善用 Chrome DevTools、Lighthouse 等協助調校效能

實際範例

1. 避免不必要的渲染:使用 React.memo

// 子元件
const TodoItem = React.memo(function TodoItem({ todo, onToggle }) {
console.log('Render:', todo.text);
return (
<li>
<input type="checkbox" checked={todo.completed} onChange={() => onToggle(todo.id)} />
{todo.text}
</li>
);
});

若未使用 React.memo,即使 todo 資料未變,只要父層重新 render,TodoItem 就會跟著重新 render。使用 React.memo 可避免這種不必要的重新渲染。


2. 函式記憶:使用 useCallback

const onToggle = useCallback((id) => {
setTodos((prev) =>
prev.map((todo) => (todo.id === id ? { ...todo, completed: !todo.completed } : todo))
);
}, []);

如果 onToggle 每次 render 都重新宣告,會導致 React.memo 判斷 props 改變,從而重新渲染子元件。使用 useCallback 可以保留函式參考的一致性。


3. 虛擬滾動列表:使用 react-window

import { FixedSizeList as List } from 'react-window';

const Row = ({ index, style }) => <div style={style}>Row {index}</div>;

const MyList = () => (
<List height={300} itemCount={1000} itemSize={35} width={300}>
{Row}
</List>
);

react-window 提供虛擬滾動的能力,只 render 可視範圍內的項目,大幅減少 DOM 結點,提高大數據列表效能。


4. 懶載入元件:使用 React.lazy

import React, { Suspense } from 'react';

const Chart = React.lazy(() => import('./Chart'));

function Dashboard() {
return (
<Suspense fallback={<div>Loading chart...</div>}>
<Chart />
</Suspense>
);
}

將大型組件分割成懶載入模組,可避免初次載入體積過大,提升頁面初始加載速度。


5. 使用 Profiler 分析效能瓶頸

React DevTools 提供 Profiler 模組,可追蹤各元件 render 時間與次數,有助於識別過度渲染或效能低落的元件。

import { Profiler } from 'react';

<Profiler
id="TodoList"
onRender={(id, phase, actualDuration) => {
console.log(`${id} rendered in ${actualDuration}ms`);
}}
>
<TodoList todos={todos} />
</Profiler>;

結語

React 效能優化並非一蹴可幾,而是需隨著應用規模與需求不斷調整與改善的過程。透過理解 Virtual DOM 的運作原理、掌握各種 Hook 的特性,以及活用分析工具,我們可以更有策略地針對效能瓶頸逐步優化,打造流暢且可維護的使用者體驗。

建議從小型優化(如 React.memouseCallback)著手,並逐步引入懶載入與虛擬化等進階技巧,讓 React 應用能夠隨著功能擴展持續保持高效能。

如果你對特定效能問題有興趣,例如圖片載入優化、CSR vs SSR 效能比較等,也可以再深入探討不同的進階主題。

React 效能優化 SOP 檢核清單入門教學筆記 | 學習筆記

· 閱讀時間約 6 分鐘
kdchang

前言

在大型單頁應用(SPA)與複雜互動式介面中,效能瓶頸常常來自不必要的重新渲染、大型 bundle 導致的載入緩慢,以及過度操作 DOM 所造成的卡頓。建立一份標準化的「效能優化檢核清單」(SOP,Standard Operating Procedure),能夠在開發流程中明確指出應檢查的重點、落實最佳實踐,並透過持續監控與回饋,進一步強化團隊的效能意識與程式品質。

本篇筆記將依照從「程式撰寫到部署」的不同階段,提出具體的檢核項目,並搭配最常見的 React 效能優化技術範例,協助你快速掌握如何在日常開發與 Code Review 中落實效能優化。


重點摘要

  • 一、避免不必要的重新渲染

    • 使用 React.memo 包裹純函式元件
    • 針對函式與物件 props,使用 useCallbackuseMemo 進行記憶
    • 避免 JSX inline 宣告函式或物件
  • 二、State 管理與元件分離

    • 下放 state 至影響範圍最小的元件
    • UI 狀態(開關、Modal 等)與業務資料分離
    • 避免全域 context 過度包覆,導致大範圍 re-render
  • 三、列表與大量資料渲染優化

    • 確保 key 穩定(使用唯一 id,非 index)
    • 採用虛擬滾動(react-windowreact-virtualized
    • 分頁或懶加載機制
  • 四、Code Splitting 與懶載入

    • 使用 React.lazy + Suspense 分割大型元件
    • 路由層級拆分,動態 import()
    • 圖片與第三方資源延遲加載
  • 五、效能分析與監控工具

    • React DevTools Profiler:分析元件 render 次數與耗時
    • Lighthouse / Web Vitals:追蹤 FCP、LCP、TTFB 等指標
    • Bundle 分析(Webpack Bundle Analyzer、Source Map Explorer)
  • 六、CI/Code Review 效能檢查

    • 將檢核清單納入 Pull Request 模板
    • 自動化檢測 bundle size 變化
    • 定期性能測試腳本(Cypress、Playwright + Lighthouse)

實際範例

範例一:避免不必要的重新渲染

// ChildComponent.jsx
import React from 'react';

function ChildComponent({ data, onClick }) {
console.log('ChildComponent render');
return <div onClick={onClick}>{data.text}</div>;
}

export default React.memo(ChildComponent);
// ParentComponent.jsx
import React, { useState, useCallback, useMemo } from 'react';
import ChildComponent from './ChildComponent';

export default function ParentComponent({ initialData }) {
const [count, setCount] = useState(0);

// useMemo 記憶 data 物件,避免因父組件重新 render 而改變 reference
const data = useMemo(
() => ({
text: initialData,
}),
[initialData]
);

// useCallback 記憶函式,不會因為 count 變化而重新建立
const handleClick = useCallback(() => {
setCount((c) => c + 1);
}, []);

return (
<div>
<p>點擊次數:{count}</p>
<ChildComponent data={data} onClick={handleClick} />
</div>
);
}

檢核點

  • ChildComponent 是否用 React.memo 包裹?
  • data 物件是否用 useMemo
  • onClick 是否用 useCallback

範例二:列表虛擬化

// ListView.jsx
import React from 'react';
import { FixedSizeList as List } from 'react-window';

const Row = React.memo(({ index, style }) => <div style={style}>列表項目 #{index}</div>);

export default function ListView() {
return (
<List height={400} itemCount={10000} itemSize={35} width={'100%'}>
{Row}
</List>
);
}

檢核點

  • 是否針對長列表導入虛擬化?
  • itemSize 與 height 設定是否合理?

範例三:Code Splitting 與懶載入

// routes.jsx
import React, { Suspense } from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';

const Home = React.lazy(() => import('./pages/Home'));
const Dashboard = React.lazy(() => import('./pages/Dashboard'));

export default function AppRouter() {
return (
<Router>
<Suspense fallback={<div>載入中...</div>}>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/dashboard" component={Dashboard} />
</Switch>
</Suspense>
</Router>
);
}

檢核點

  • 是否有針對路由或大型元件進行懶載入?
  • fallback UI 是否友善?

範例四:效能分析

import React, { Profiler } from 'react';
import TodoList from './TodoList';

function onRenderCallback(id, phase, actualDuration) {
console.log(`${id} ${phase} 耗時:${actualDuration.toFixed(2)}ms`);
}

export default function App() {
return (
<Profiler id="TodoList" onRender={onRenderCallback}>
<TodoList />
</Profiler>
);
}

檢核點

  • 是否使用 Profiler 區隔並記錄核心元件耗時?
  • 是否定期檢視開發者工具數據?

總結

以上檢核清單涵蓋了從程式撰寫、元件切分,到效能分析與持續監控的各個面向。建議將此清單整合至 Pull Request 模板中,並在團隊中推廣效能優化文化。持續在日常開發中落實這些檢查,能確保應用在功能增長的同時仍保持流暢的使用者體驗,並降低潛在的性能退化風險。若需將本文轉為 Markdown、PDF 或 Notion 模板,歡迎隨時提出。

補充:React 效能優化 SOP 檢核清單

一、避免不必要的重新渲染

  • 是否使用 React.memo 包裹純函式元件?
  • 是否有使用 useCallback 記憶傳遞的函式 props?
  • 是否有使用 useMemo 記憶計算結果,避免重複計算?
  • 是否避免在 JSX 中直接宣告函式或物件(例如 inline style)?

二、State 管理與元件分離

  • 是否將狀態下放至最小影響範圍的元件中?
  • 是否避免使用不必要的 lifting state up?
  • 是否將 UI 狀態(如開關、hover 狀態)與全域狀態分離?

三、Props 傳遞與結構優化

  • 是否控制 props 深層傳遞導致的層層 re-render?
  • 是否 props 結構穩定、可預期?(避免 object/array 每次都變)

四、渲染大量資料時的處理

  • 是否針對長列表使用虛擬化工具(如 react-window, react-virtualized)?
  • 是否有合理使用 key(且為穩定值,例如 id 而非 index)?

五、資源載入與 Code Splitting

  • 是否使用 React.lazy + Suspense 實現元件懶載入?
  • 是否進行 route-based code splitting(使用動態 import)?
  • 是否有壓縮圖片、延遲圖片載入(lazy loading)?

六、效能監控與分析

  • 是否使用 React DevTools Profiler 檢查 render 頻率與時間?
  • 是否分析過 Lighthouse / Web Vitals 的效能指標?
  • 是否檢查 Bundle Size(Webpack 分析工具、SourceMap Explorer 等)?

七、避免常見陷阱

  • 是否避免每次 render 都新建匿名函式?
  • 是否避免重複 render 同一資料來源?
  • 是否避免過度依賴 context 導致全頁重 render?

八、開發階段優化習慣

  • 是否將效能優化納入 Code Review 檢查點?
  • 是否每個大型新元件都確認是否會引起不必要渲染?
  • 是否測試過主流程在弱網或低效能設備上的表現?