跳至主要内容

Bloom Filter 入門教學筆記 | 學習筆記

· 閱讀時間約 4 分鐘
kdchang

前言

在處理大數據或需要大量查詢的系統中,「是否存在某個元素」是一個常見的需求。例如:檢查某個使用者是否已註冊、某筆資料是否已儲存、或某個網址是否在黑名單中。這些操作如果都直接查詢資料庫,會造成效能瓶頸,尤其在資料量非常大的情況下。

為了解決這個問題,Bloom Filter(布隆過濾器)誕生了。這是一種空間效率極高且速度快速概率型資料結構,用於檢查元素是否在集合中。它的特點是可以節省大量記憶體與查詢時間,代價是可能會有「誤判存在」的情況,但保證不會漏判。

Bloom Filter 並不是用來取代資料庫,而是作為第一層快速判斷工具,幫助系統更有效率地篩選資料,提升整體效能。


重點摘要

  • 定義:Bloom Filter 是一種用於判斷元素是否存在於集合中的資料結構,具有一定誤判率。

  • 特點

    • 省記憶體:只使用位元陣列來表示集合
    • 查詢速度快:時間複雜度為 O(k),k 為 hash 函數數量
    • 有誤判率:可能錯誤地認為某元素存在,但不會錯誤地排除已存在的元素
    • 不可刪除:傳統 Bloom Filter 不支援元素刪除(可改用 Counting Bloom Filter)
  • 運作方式

    1. 建立一個 bit array(位元陣列),初始為全 0

    2. 每個元素經由多個 hash 函數映射為數個 bit 位址,並設為 1

    3. 查詢元素時,將元素經過相同的 hash 函數,檢查對應 bit 是否都為 1

      • 若任一為 0,則元素一定不存在
      • 若全部為 1,則元素可能存在
  • 應用場景

    • 快取系統的查詢優化
    • URL 黑名單篩選
    • 防止 email 重複註冊
    • 分散式系統中的資源去重

實際範例(Python 語言)

以下示範如何使用 Python 實作 Bloom Filter,並透過 pybloom-live 套件進行簡易操作。

安裝套件

pip install pybloom-live

基本使用範例

from pybloom_live import BloomFilter

# 建立一個 Bloom Filter,容量為 10000 筆,容忍誤判率為 1%
bloom = BloomFilter(capacity=10000, error_rate=0.01)

# 加入元素
bloom.add("apple")
bloom.add("banana")

# 查詢元素是否存在
print("apple" in bloom) # True
print("banana" in bloom) # True
print("grape" in bloom) # False(或可能 True,機率很小)

解釋程式碼

  • capacity: 預估將要儲存的元素數量,Bloom Filter 的大小與 hash 數量會依此設定
  • error_rate: 容許誤判率(預測某元素在集合中時,實際不在)
  • .add(): 將元素加入 Bloom Filter
  • in: 使用 Python 語法糖來查詢元素是否在 Bloom Filter 中

模擬誤判情況

false_positives = 0
test_data = ["item" + str(i) for i in range(10000, 10100)]

for item in test_data:
if item in bloom:
false_positives += 1

print(f"誤判次數:{false_positives}")

這段程式碼將檢查 100 筆從未加入過的資料,看有多少筆被錯誤地判斷為「已存在」。由於設定誤判率為 1%,實際誤判筆數大致會落在 0~2 筆之間。


進階補充:Bloom Filter 的數學基礎

假設:

  • n 為預估儲存的元素數量
  • m 為 bit 陣列大小
  • k 為 hash 函數數量

則誤判率約為:

(1 - e^(-kn/m))^k

透過微積分分析,可以得出在給定 mn 的情況下,最理想的 hash 函數數量為:

k = (m/n) * ln(2)

Bloom Filter 的實作常會根據這些公式自動配置最佳參數。


總結

Bloom Filter 是一個非常實用的資料結構,適合用於需要高效率查詢與記憶體節省的場景。雖然它無法完全準確地判斷元素是否存在,但在可以接受少量誤判的場景(例如快取、URL 過濾、黑名單系統等)中,它是非常有效率的選擇。

理解 Bloom Filter 的基本概念與限制,可以幫助我們在設計大型系統時做出更合適的架構選擇。如果你正在處理大量資料或需要快速查詢的場景,不妨試著將 Bloom Filter 納入考量。

CSS font-size 單位介紹入門教學筆記 | 學習筆記

· 閱讀時間約 3 分鐘
kdchang

在 CSS 中,pxrememvh 都可以用來設定 font-size,但它們的用途和適用場景不同。以下是詳細解析與使用建議:


1️. px(像素,固定單位)

特性

  • 絕對單位,與螢幕解析度無關。
  • 不會受父元素影響,設定多少就是多少。

適用場景

精確控制字體大小,例如:

  • Logo、按鈕字體
  • 小型 UI 元素(如標籤、提示訊息)
  • 避免字體大小變動的情境

範例

h1 { font-size: 32px; }  /* 確保標題大小為 32px */
button { font-size: 14px; } /* UI 按鈕文字固定 14px */

何時避免?

  • 不利於 響應式設計,無法根據使用者的設定(如無障礙放大字體)自適應。

2️. rem(Root EM,根元素相對單位)

特性

  • 參照 html 根元素font-size 來計算。
  • 預設 htmlfont-size16px(可透過 html { font-size: 10px; } 調整)。
  • 可用於全站統一調整字體大小。

適用場景

全站一致字體設計,方便調整:

  • 段落、標題(H1-H6)
  • 文章內容
  • 大部分標準排版

範例

html { font-size: 16px; }  /* 設定根元素大小 */
p { font-size: 1.5rem; } /* 16px * 1.5 = 24px */

只要改變 htmlfont-size,所有 rem 設定的字體大小都會一起調整。

何時避免?

  • 局部組件內縮放時,可能需要用 em 而不是 rem

3️. em(相對單位)

特性

  • 相對於 父元素的 font-size 來計算。
  • 父層字體大小變大,子層 em 設定的大小也會跟著變。

適用場景

組件內部的字體調整,適用於:

  • 按鈕、卡片、區塊內的標題
  • 排版時要根據父層調整的字體
  • 不同區域需要稍微變大的字體

範例

.container { font-size: 20px; }  /* 設定父層大小 */
p { font-size: 1.2em; } /* 20px * 1.2 = 24px */

.container font-size 改變時,內部 p 的大小也會跟著變。

何時避免?

  • 多層 em 巢狀結構時,字體大小可能會變得難以控制。

4️. vh(視窗高度單位)

特性

  • 1vh 代表 視窗高度的 1%
  • 適合動態字體大小,可以讓字體隨視窗大小變化。

適用場景

全螢幕標題或 Hero 文字

  • 首頁橫幅標題
  • 全螢幕展示頁面
  • 需隨裝置變化的動態字體

範例

h1 { font-size: 10vh; }  /* 視窗高度的 10% */

當視窗縮小,字體會自動變小。

何時避免?

  • 文字變化太大時,可能導致小螢幕閱讀困難。

如何選擇?

單位依據優點缺點適用場景
px固定大小精確控制、不變動無法適應不同裝置Logo、按鈕、小 UI 元件
rem根元素大小可全站統一調整父層無影響,局部調整時需考慮文章內容、標題、標準排版
em父層大小組件內可相對調整巢狀結構時較難控制按鈕、卡片區塊內標題
vh視窗高度可隨視窗變化小螢幕可能太小、大螢幕可能過大全螢幕標題、動態字體

最佳實踐

一般網站內容

  • 使用 rem 設定主字體,確保一致性:
    html { font-size: 16px; }  
    p { font-size: 1rem; } /* 16px */
    h1 { font-size: 2rem; } /* 32px */

局部 UI 組件

  • 使用 em,確保字體相對變化:
    .button { font-size: 1em; }  /* 依據父層大小 */

全螢幕標題

  • 使用 vh,確保字體適應畫面:
    .hero-title { font-size: 8vh; }

特殊 UI(固定大小)

  • 使用 px,避免字體縮放:
    .logo { font-size: 24px; }

總結

  • 固定字體px
  • 全站一致性rem
  • 組件內相對縮放em
  • 視窗自適應vh

若有特定的設計需求或專案需求,可以再根據需求細調合適的單位。

Vue 正確新增或修改物件屬性入門教學筆記 | 學習筆記

· 閱讀時間約 2 分鐘
kdchang

前言

在 Vue 2(Options API)中,this.$set 是用來在響應式系統中正確新增或修改物件屬性的方法。這對於動態新增屬性或修改陣列的指定索引值特別有用。


語法

this.$set(target, propertyName/index, value)
  • target:要修改的對象或陣列
  • propertyName(物件)或 index(陣列)
  • value:要設的值

為什麼需要 this.$set

Vue 2 的 reactivity(響應式系統)使用 Object.defineProperty,它無法偵測到新加的屬性或是直接對陣列用索引來賦值。

例如,下面的程式碼是不會觸發畫面更新的:

this.someObj.newKey = 'value'      // 不會觸發更新
this.someArray[1] = 'changed' // 不會觸發更新

必須改成:

this.$set(this.someObj, 'newKey', 'value')      // 會觸發更新
this.$set(this.someArray, 1, 'changed') // 會觸發更新

範例一:對物件新增屬性

data() {
return {
user: {
name: '小明'
}
}
},
mounted() {
this.$set(this.user, 'age', 30);
}

這樣才能讓 user.age 成為響應式屬性,更新時畫面才會重新渲染。


範例二:修改陣列中的值

data() {
return {
items: ['a', 'b', 'c']
}
},
methods: {
updateItem() {
this.$set(this.items, 1, 'changed');
}
}

Vue 3 呢?

在 Vue 3 裡,因為 reactivity 系統改用 Proxy,所以可以直接新增或修改屬性,不需要 this.$set 了:

this.someObj.newKey = 'value'   // Vue 3 沒問題
this.someArray[1] = 'changed' // Vue 3 沒問題

如果你正在寫 Vue 2 的 Tic Tac Toe 遊戲,當你要動態更新棋盤的某個格子時,這樣做就是對的:

this.$set(this.board[row], col, 'X');

這樣才能確保畫面能即時更新。

JSX 入門教學筆記 | 學習筆記

· 閱讀時間約 3 分鐘
kdchang

1. JSX 是什麼?

JSX(JavaScript XML)是一種語法擴展,主要用於 React 函式庫中,它允許在 JavaScript 代碼中撰寫類似 HTML 的語法,使 UI 組件的編寫更加直觀。JSX 並非原生 JavaScript,需要透過 Babel 轉譯成標準 JavaScript 代碼。

2. 為什麼使用 JSX?

JSX 提供了一種更加可讀、直觀的方式來描述 UI 結構,相比於傳統的 JavaScript DOM 操作,它更簡潔易懂。此外,JSX 具備以下優勢:

  • 可讀性高:類似 HTML 的語法使 UI 組件結構清晰。
  • 與 JavaScript 無縫結合:可在 JSX 中嵌入 JavaScript 表達式。
  • 更安全:React 會自動處理 XSS(跨站腳本攻擊),確保數據安全。
  • 高效渲染:React 使用虛擬 DOM 最小化真實 DOM 更新,提高性能。

3. 基本語法

3.1 基本範例

JSX 允許我們在 JavaScript 代碼中使用類似 HTML 的語法來描述 UI:

const element = <h1>Hello, JSX!</h1>;
ReactDOM.render(element, document.getElementById('root'));

3.2 JSX 中的 JavaScript 表達式

JSX 允許在 {} 中插入 JavaScript 表達式,例如變數、函式調用等。

const name = "Alice";
const element = <h1>Hello, {name}!</h1>;

3.3 JSX 屬性

JSX 屬性與 HTML 類似,但有些屬性名稱需使用 camelCase,例如 className 取代 classhtmlFor 取代 for

const element = <input type="text" placeholder="輸入文字" className="input-box" />;

3.4 JSX 內聯樣式

內聯樣式需使用 JavaScript 對象,且屬性名稱為 camelCase。

const style = { color: 'blue', fontSize: '20px' };
const element = <p style={style}>這是一段藍色文字</p>;

4. JSX 中的條件與循環

4.1 條件渲染(if...else)

JSX 本身不支援 if...else,需使用三元運算子或變數。

const isLoggedIn = true;
const element = isLoggedIn ? <h1>歡迎回來!</h1> : <h1>請登入</h1>;

4.2 使用 && 運算符

const messages = ['新訊息1', '新訊息2'];
const element = (
<div>
{messages.length > 0 && <p>你有 {messages.length} 則未讀訊息。</p>}
</div>
);

4.3 迴圈渲染(map)

JSX 可透過 map 方法來動態渲染列表。

const list = ['Apple', 'Banana', 'Cherry'];
const element = (
<ul>
{list.map((item, index) => <li key={index}>{item}</li>)}
</ul>
);

5. JSX 與 React 組件

JSX 可與 React 組件結合使用,提升 UI 開發的模組化程度。

5.1 函式型組件

function Welcome(props) {
return <h1>你好,{props.name}</h1>;
}

const element = <Welcome name="小明" />;
ReactDOM.render(element, document.getElementById('root'));

5.2 類別型組件

class Welcome extends React.Component {
render() {
return <h1>你好,{this.props.name}</h1>;
}
}

const element = <Welcome name="小明" />;
ReactDOM.render(element, document.getElementById('root'));

6. 總結

JSX 是 React 開發的重要語法,它能夠讓 UI 描述更加直觀,並與 JavaScript 無縫整合。透過學習 JSX,我們可以更高效地撰寫可重用的 React 組件,提高開發效率。

正規表達式(Regex)入門教學筆記 | 學習筆記

· 閱讀時間約 3 分鐘
kdchang

1. 正規表達式簡介

正規表達式(Regular Expression,簡稱 Regex)是一種用於字串匹配與處理的強大工具。它廣泛應用於文字搜尋、資料驗證、文字替換等場景。正規表達式透過特殊的語法模式來描述字串結構,並可被多種程式語言支援,如 Python、JavaScript、Java、C# 等。

2. 基本語法與範例

2.1 字面量匹配

正規表達式最基本的形式是字面量匹配,意即直接搜尋特定字串。

範例

hello

可匹配「hello」這個字串。

2.2 字元類別(Character Classes)

字元類別用來匹配特定類型的字元。

語法說明範例
.任意單一字元(不含換行)h.t 可匹配 hat, hot, hit
[abc]任意列出的字元[aeiou] 可匹配任一母音字母
[^abc]不包含列出的字元[^0-9] 可匹配任何非數字字元
[a-z]字母範圍[a-zA-Z] 可匹配所有英文字母
\d任意數字(等價於 [0-9]\d{2} 可匹配 23, 89
\w任意字母、數字或底線([a-zA-Z0-9_]\w+ 可匹配 hello_123
\s空白字元(空格、Tab、換行)\s+ 可匹配

2.3 邊界匹配(Anchors)

用來限制匹配的位置。

語法說明範例
^開頭^Hello 只能匹配 Hello world,但不匹配 Say Hello
$結尾world$ 只能匹配 Hello world,但不匹配 world today
\b單字邊界\bcat\b 只匹配 cat,但不匹配 catch

2.4 重複匹配(Quantifiers)

用來指定重複次數。

語法說明範例
*0 次或更多ba* 可匹配 b, ba, baa, baaa
+1 次或更多ba+ 可匹配 ba, baa, baaa 但不匹配 b
?0 次或 1 次colou?r 可匹配 colorcolour
{n}恰好 n 次a{3} 只匹配 aaa
{n,}至少 n 次a{2,} 可匹配 aa, aaa, aaaa
{n,m}n 到 m 次a{2,4} 可匹配 aa, aaa, aaaa

2.5 分組與選擇(Groups & Alternation)

使用括號來分組,使用 | 來表示選擇。

範例

(grape|apple|banana)

匹配 grapeapplebanana

範例(分組)

(a|b)c

匹配 acbc

2.6 轉義字元(Escape Characters)

正規表達式中的特殊字元(如 .*?)需要用 \ 進行轉義。

範例

\.com

匹配 .com(實際字面值)。

3. 正規表達式應用範例

3.1 Email 驗證

^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$

匹配範例

  • test@example.com
  • hello@domain.co.uk
  • invalid@com

3.2 手機號碼驗證(台灣格式)

^09[0-9]{8}$

匹配範例

  • 0912345678
  • 1234567890

3.3 找出所有網址

https?://[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(/\S*)?

匹配範例

  • https://www.google.com
  • http://example.org/test
  • ftp://invalid.url

3.4 HTML 標籤匹配

<([a-z]+)([^<]+)*(?:>(.*?)</\1>|/>)

匹配範例

  • <div class="container">Content</div>
  • <img src="image.jpg" />

4. 總結

正規表達式是一種強大的工具,適用於文字處理與資料驗證。透過學習基本語法與實際應用,你可以更有效率地處理字串相關的問題。實務上建議透過線上工具(如 Regex101 等工具)來測試你的正規表達式,以加深理解。

Backend For Frontend(BFF)入門教學筆記 | 學習筆記

· 閱讀時間約 5 分鐘
kdchang

前言

當今前端與後端開發越趨分離,加上行動裝置、多樣化的使用者端(Web、App、IoT 等)快速成長,Backend for Frontend(BFF) 架構逐漸成為微服務與現代應用中不可或缺的一環。本文將介紹 BFF 的基本概念、優缺點、使用情境與實際範例,幫助你快速理解並在專案中應用。

一、什麼是 Backend For Frontend?

Backend For Frontend(簡稱 BFF) 是一種後端架構模式,其核心理念是:每種前端應用(Web、Mobile App、Smart TV)都由各自專屬的後端服務來支援,這個後端只為對應的前端量身打造。

傳統系統中,前端直接呼叫後端 API 或微服務,常會遇到以下問題:

  • 回傳資料過多(浪費頻寬)
  • 回傳資料不夠(還要多次請求)
  • 錯誤格式(不適合前端解析)
  • 缺乏聚合邏輯(需要組裝多個 API 回應)

BFF 則專門為特定前端處理這些問題,讓資料更適合顯示、傳輸與渲染,減少耦合與重複工作。


二、BFF 架構示意圖

          +---------------+          +-------------------+
Web App → | BFF for Web | → REST | User Service |
+---------------+ +-------------------+

+----------------+ | REST / RPC
Mobile → | BFF for Mobile | → GraphQL| Order Service |
+----------------+ |

Product Service

每一個 BFF 可以:

  • 聚合多個微服務的資料
  • 轉換資料格式供前端使用
  • 處理權限與驗證邏輯(例如 token 驗證)
  • 實作快取策略(如 CDN, Redis)
  • 提供更穩定的 API 給前端

三、什麼情況適合使用 BFF?

  1. 有多種前端平台:Web 和 App 須分別調整 API。
  2. 資料需聚合多個來源:例如組合訂單資料與商品詳細資訊。
  3. 前端需要特別的資料結構或格式轉換
  4. 希望讓前端開發獨立部署、測試、開發
  5. 希望簡化前端邏輯,把複雜邏輯搬到後端處理

四、BFF 優點與缺點

優點

  • 前後端明確分工,降低耦合
  • 加快前端開發速度
  • 可根據裝置特性量身打造 API
  • 改善效能(避免多餘資料,提升快取)
  • 更容易實施權限控管與安全策略

缺點

  • 增加部署與維護成本(每個前端都需對應 BFF)
  • 需要資源維護 BFF 團隊
  • 需協調不同 BFF 的資料一致性與邏輯重複問題

五、實際範例:Node.js 建立一個 BFF

假設有以下微服務:

  • GET /users/:id 回傳使用者資料
  • GET /orders/user/:id 回傳該用戶的訂單清單

現在我們為 Web 建立一個 BFF,統一提供 /profile/:id 接口,回傳使用者基本資訊與訂單清單。

BFF 架構(Node.js + Express)

// bff/web-server.js
const express = require('express');
const axios = require('axios');
const app = express();

app.get('/profile/:id', async (req, res) => {
const userId = req.params.id;

try {
const [userRes, ordersRes] = await Promise.all([
axios.get(`http://users-service/users/${userId}`),
axios.get(`http://orders-service/orders/user/${userId}`)
]);

res.json({
user: userRes.data,
orders: ordersRes.data
});
} catch (error) {
console.error(error);
res.status(500).send('資料取得失敗');
}
});

app.listen(3000, () => {
console.log('BFF for Web running at http://localhost:3000');
});

使用說明

  • 前端只需呼叫 GET /profile/123 即可拿到整合後的資料。
  • BFF 內部負責跟不同服務串接、組裝、錯誤處理與轉換格式。

六、BFF 和 GraphQL 的關係

GraphQL 本身也常用於 BFF 的實作方式之一。透過 GraphQL:

  • 前端可以自己定義要的欄位(防止 over-fetch)
  • 可以統一查詢不同來源資料(類似 BFF 的聚合邏輯)
  • 可搭配 Apollo Server 快速搭建

BFF 可以使用 REST,也可以使用 GraphQL,取決於前後端的需求與團隊熟悉度。


七、延伸應用:BFF 搭配微前端(Micro Frontend)

當前端本身也是多團隊分工、模組化(例如大型企業的後台管理系統),BFF 可以依照模組拆分,例如:

  • Order 模組對應一個 BFF
  • Admin 模組對應另一個 BFF

讓後端資料與前端 UI 模組相對應,利於組織協作與部署。


八、總結

面向傳統 APIBFF 架構
資料格式後端決定針對前端定製
跨平台支援不佳良好
前端彈性
擴充性
成本中~高(需維護 BFF)

BFF 並不是萬靈丹,但在多平台、多樣前端需求的專案中,非常實用,能夠有效地降低溝通成本、提升開發效率,並提供更佳的 API 使用體驗。

如果你的團隊正在建構大型應用系統,或前端開發頻繁改動,不妨考慮導入 BFF 架構,讓前後端協作更流暢、更可維護。

參考文件

  1. Backend For Frontend 前端模式的後端
  2. 前端開發者該負責寫 API Endpoints 嗎?The Backend For Frontend Pattern (BFF) In Microservices World
  3. Backends for Frontends pattern
  4. Backend for frontend (BFF) pattern— why do you need to know it?

前端渲染模式入門教學筆記:CSR、SSR、SSG、ISR、DPR | 學習筆記

· 閱讀時間約 4 分鐘
kdchang

前言

隨著前端框架如 React、Vue、Next.js、Nuxt 的普及,網站渲染方式也變得更加多樣化。不再只是單純地將 HTML 靜態輸出,開發者可依據產品需求選擇合適的渲染模式,如 CSR(Client Side Rendering)、SSR(Server Side Rendering)、SSG(Static Site Generation)、ISR(Incremental Static Regeneration)和 DPR(Distributed Persistent Rendering)。這些策略在效能、SEO、使用者體驗與部署維運上各有優劣。

本篇將簡明扼要介紹這些渲染方式的基本概念、特性差異與適用情境,並透過簡單範例說明。


重點摘要

  • CSR(Client Side Rendering)

    • 完整渲染交由瀏覽器執行
    • 初始速度慢但互動性高
    • 適合 SPA 應用與登入後系統
  • SSR(Server Side Rendering)

    • 首次請求由伺服器生成 HTML
    • 較佳的 SEO 與初次載入速度
    • 適合需要即時資料更新與 SEO 的網站
  • SSG(Static Site Generation)

    • 編譯階段預先產出 HTML
    • 非常快、可搭配 CDN 快取
    • 適合內容固定或更新頻率低的網站
  • ISR(Incremental Static Regeneration)

    • 結合 SSG 和 SSR 優勢
    • 預先生成 + 背景重新生成
    • 適合部份動態又希望維持快取效能的頁面
  • DPR(Distributed Persistent Rendering)

    • 通常指 Vercel、Netlify 的延遲生成與儲存機制
    • 第一次請求時產生頁面並儲存供後續使用
    • 可視為 ISR 的延伸架構

各種渲染方式概念與範例

1. Client Side Rendering(CSR)

概念: 網站載入時只傳送一個空的 HTML 檔案和 JavaScript 程式,資料由瀏覽器下載後執行渲染。常見於 React、Vue 等 SPA 應用。

優點:

  • 使用者體驗流暢,適合高度互動介面
  • 遷移頁面不需重新載入

缺點:

  • 首次載入慢,對 SEO 不利
  • 如果 JS 載入失敗,畫面會呈現空白

範例:

// React CSR 應用
import React from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';

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

部署後的 HTML 初始如下:

<html>
<head></head>
<body>
<div id="root"></div>
<script src="bundle.js"></script>
</body>
</html>

2. Server Side Rendering(SSR)

概念: 每次請求時,伺服器動態產生完整 HTML 頁面並傳給瀏覽器。Next.js 等框架支援 SSR。

優點:

  • 快速呈現內容,對 SEO 友善
  • 可根據請求產出客製化內容

缺點:

  • 每次請求都需重新計算,伺服器壓力大
  • 開發與維護相對複雜

範例(Next.js)

export async function getServerSideProps() {
const res = await fetch('https://api.example.com/data');
const data = await res.json();
return { props: { data } };
}

export default function Page({ data }) {
return <div>{data.title}</div>;
}

3. Static Site Generation(SSG)

概念: 在建置階段預先產生所有 HTML 頁面,部署後為靜態檔案,可用 CDN 快取。

優點:

  • 載入速度快
  • 可用於免費 CDN,例如 GitHub Pages、Vercel

缺點:

  • 無法處理頻繁變動的資料
  • 若內容變更需重新部署

範例(Next.js)

export async function getStaticProps() {
const res = await fetch('https://api.example.com/data');
const data = await res.json();
return { props: { data } };
}

export default function Page({ data }) {
return <div>{data.title}</div>;
}

4. Incremental Static Regeneration(ISR)

概念: 在 SSG 基礎上增加「背景重新建置」能力。首次請求使用舊版頁面,背景靜默更新最新版本。

優點:

  • 同享 SSG 的快取效能
  • 同時具備動態更新能力

缺點:

  • 實作較複雜,需要平台支援(如 Next.js on Vercel)

範例(Next.js)

export async function getStaticProps() {
const res = await fetch('https://api.example.com/data');
const data = await res.json();
return {
props: { data },
revalidate: 60, // 每 60 秒可重新生成一次
};
}

5. Distributed Persistent Rendering(DPR)

概念: 首度請求頁面時即時產生內容並儲存於分散式快取(如 CDN 或 edge network),後續請求使用快取內容。

優點:

  • 初次請求稍慢,但之後快如 SSG
  • 不需事先建構所有頁面(省資源)

缺點:

  • 首次請求延遲較高
  • 需仰賴平台(如 Vercel 的 On-Demand ISR)

範例: 在 Next.js 中使用 revalidate: 'on-demand' 需搭配 Vercel 平台設定。


各渲染方式比較表

渲染方式初次速度SEO 友善動態內容開發複雜度適用場景
CSRSPA、登入後介面
SSR中等部落格、商品頁
SSG文件、靜態頁面
ISR新聞、部份變動頁面
DPR首次慢後快海量頁面動態生成

總結

選擇正確的渲染模式,取決於你的網站目標、使用情境與部署資源。對於希望提升初次載入速度與 SEO 的內容網站,SSR 或 SSG 是合適的選擇;若偏重互動性或使用者登入後操作,CSR 更適合。對於需要兼顧靜態快取與動態更新的情境,ISR 或 DPR 提供了良好的平衡。

React Redux 介紹入門教學筆記 | 學習筆記

· 閱讀時間約 4 分鐘
kdchang

1. 什麼是 Redux?

Redux 是 JavaScript 應用程式的狀態管理工具,最常與 React 搭配使用。它提供一個單一的全域狀態樹,使應用程式的狀態變更更可預測、可測試、可維護。Redux 遵循 單向數據流不可變狀態 的概念,適合管理大型應用的複雜狀態。


2. Redux 核心概念

Redux 的運作基礎主要由三個部分組成:

  1. Store(存儲狀態的地方)
    • 整個應用程式的狀態存儲在單一的 Store 中。
  2. Action(動作)
    • Action 是一個 JavaScript 物件,描述了「發生了什麼事」。通常包含 type 屬性來表明事件類型,並可能包含 payload 傳遞額外數據。
  3. Reducer(狀態變更邏輯)
    • Reducer 是一個純函式,接收當前狀態與 Action,根據 Action 類型來決定如何更新狀態。

3. 安裝 Redux 和 React-Redux

在 React 專案中安裝 Redux 及 React-Redux:

npm install @reduxjs/toolkit react-redux

Redux Toolkit(RTK)是官方推薦的 Redux 工具包,它提供更簡潔、易用的 API 來簡化 Redux 使用。


4. 創建 Redux Store

store.js 檔案中,使用 configureStore 創建 Store:

import { configureStore } from "@reduxjs/toolkit";
import counterReducer from "./counterSlice";

const store = configureStore({
reducer: {
counter: counterReducer,
},
});

export default store;

在這裡,我們將 counterReducer 設定為 counter 這個 slice 的 reducer。


5. 創建 Slice(Reducer + Action)

counterSlice.js 檔案中,使用 createSlice 定義 counter 相關的狀態與 reducer:

import { createSlice } from "@reduxjs/toolkit";

const counterSlice = createSlice({
name: "counter",
initialState: { value: 0 },
reducers: {
increment: (state) => {
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
},
incrementByAmount: (state, action) => {
state.value += action.payload;
},
},
});

export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export default counterSlice.reducer;

這裡的 createSlice 幫助我們:

  • 定義狀態的初始值(initialState
  • 自動生成 reducer
  • 自動產生對應的 Action

6. 設置 Store 提供給 React

index.js 檔案中使用 Provider 讓整個 React 應用可以存取 Redux Store:

import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import store from "./store";
import App from "./App";

ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);

這樣 App 及其所有子元件都可以存取 Redux Store。


7. 在 React 組件中使用 Redux

Counter.js 中使用 Redux Store 來讀取與修改 counter 的值:

import React from "react";
import { useSelector, useDispatch } from "react-redux";
import { increment, decrement, incrementByAmount } from "./counterSlice";

const Counter = () => {
const count = useSelector((state) => state.counter.value);
const dispatch = useDispatch();

return (
<div>
<h1>計數器:{count}</h1>
<button onClick={() => dispatch(increment())}>增加</button>
<button onClick={() => dispatch(decrement())}>減少</button>
<button onClick={() => dispatch(incrementByAmount(5))}>增加 5</button>
</div>
);
};

export default Counter;

在這個範例中:

  • useSelector(state => state.counter.value) 取得 Store 內的 counter 值。
  • useDispatch() 取得 Redux 的 dispatch 函式,發送 Action 來更新狀態。
  • 點擊按鈕時,對應的 Action 會發送給 Reducer,修改狀態。

8. 使用 Redux DevTools 進行除錯

Redux Toolkit 內建支援 Redux DevTools,可以在瀏覽器中查看 Redux 狀態變更的歷史紀錄,方便除錯。安裝 Redux DevTools,並開啟瀏覽器的 Redux 分頁來觀察 Action 和 State 變化。


9. 總結

Redux 提供了一個強大的狀態管理方式,適合中大型應用。在 Redux Toolkit 的幫助下,開發 Redux 應用變得更加直觀與簡潔。本篇介紹了 Redux 的基本概念,並透過實際範例展示如何在 React 中整合 Redux。如果你的應用狀態複雜,需要跨組件共享,Redux 會是很好的選擇。

參考文件

  1. redux vs useContext, useReducer,該怎麼選擇?
  2. The Problem with React's Context API
  3. React 狀態管理套件比較與原理實現 feat. Redux, Zustand, Jotai, Recoil, MobX, Valtio
  4. 利用 React Context API + useReducer 實作 Redux

Cypress 測試入門教學筆記 | 學習筆記

· 閱讀時間約 3 分鐘
kdchang

Cypress 是一款現代化的前端自動化測試工具,主要用於測試 Web 應用程式,支援端對端(E2E)測試、元件測試等。

1.1 為何選擇 Cypress?

  • 簡單易用,直接運行於瀏覽器。
  • 提供即時回饋,方便 Debug。
  • 內建等待機制,避免手動處理異步問題。
  • 支援截圖與錄影,方便測試分析。

2. 安裝與設定

2.1 安裝 Cypress

使用 npm 或 yarn 安裝 Cypress:

npm install cypress --save-dev

yarn add cypress --dev

2.2 執行 Cypress

安裝完成後,可以使用以下指令開啟 Cypress GUI:

npx cypress open

或直接執行測試:

npx cypress run

3. 撰寫 Cypress 測試

3.1 建立測試檔案

Cypress 預設的測試檔案放置於 cypress/e2e/ 目錄下,例如:

cypress/e2e/sample_spec.cy.js

3.2 基本測試範例

describe('My First Test', () => {
it('Visits the app', () => {
cy.visit('https://example.cypress.io');
cy.contains('type').click();
cy.url().should('include', '/commands/actions');
});
});

3.3 選擇器與操作

Cypress 提供多種選擇器來查找元素,例如:

cy.get('button').click(); // 透過標籤名稱選擇
cy.get('.btn-primary').click(); // 透過 class 選擇
cy.get('#submit-btn').click(); // 透過 ID 選擇

3.4 斷言(Assertions)

使用 .should() 進行斷言,例如:

cy.get('.success-message').should('be.visible');
cy.get('h1').should('have.text', 'Welcome');

4. 進階功能

4.1 錯誤處理與偵錯

Cypress 提供 cy.pause()cy.debug() 來幫助偵錯:

cy.get('button').click();
cy.pause(); // 測試暫停
cy.get('.result').should('be.visible');

4.2 假資料與 API 模擬(Mock API)

可以使用 cy.intercept() 攔截 API 請求,例如:

cy.intercept('GET', '/api/user', { name: 'John Doe' }).as('getUser');
cy.visit('/profile');
cy.wait('@getUser');

4.3 自訂命令

可以在 cypress/support/commands.js 內定義自訂命令,例如:

Cypress.Commands.add('login', (email, password) => {
cy.get('#email').type(email);
cy.get('#password').type(password);
cy.get('button[type="submit"]').click();
});

然後在測試中使用:

cy.login('test@example.com', 'password123');

5. CI/CD 整合

Cypress 可以與 CI/CD 工具整合,如 GitHub Actions 或 GitLab CI/CD。

5.1 GitHub Actions 設定範例

.github/workflows/cypress.yml 中新增:

name: Cypress Tests
on: [push, pull_request]

jobs:
cypress-run:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Install dependencies
run: npm install
- name: Run Cypress tests
run: npx cypress run

6. 總結

透過 Cypress,可以輕鬆進行 Web 應用的測試,提高開發效率與穩定性。建議多練習不同測試場景,如表單驗證、API 測試與 RWD 測試,以熟悉 Cypress 強大的測試能力。

Node.js 入門教學筆記 | 學習筆記

· 閱讀時間約 3 分鐘
kdchang

Node.js 是一個基於 Chrome V8 JavaScript 引擎的運行環境,可讓開發者使用 JavaScript 來撰寫後端程式。它適合用於構建高效能、非同步的網路應用。

1.1 為何選擇 Node.js?

  • 非同步 & 事件驅動:適合 I/O 密集型應用,如 Web 伺服器。
  • 單一語言開發:可用 JavaScript 同時開發前端與後端。
  • 強大的生態系統:擁有豐富的 NPM(Node Package Manager)套件。
  • 高效能:基於 V8 引擎,運行速度快。

2. 安裝與環境設定

2.1 安裝 Node.js

Node.js 官方網站 下載並安裝 LTS 版本。

2.2 檢查安裝是否成功

安裝完成後,在終端機輸入以下指令:

node -v
npm -v

應該會顯示 Node.js 和 npm(Node 套件管理工具)的版本號。

3. 基本應用程式

3.1 建立第一個 Node.js 應用

新建一個 app.js 檔案,並輸入:

console.log('Hello, Node.js!');

然後在終端機執行:

node app.js

應該會輸出:

Hello, Node.js!

3.2 建立簡單的 HTTP 伺服器

const http = require('http');

const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Hello, World!');
});

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

執行後,開啟瀏覽器並訪問 http://localhost:3000,應該會看到 Hello, World!

4. Node.js 模組

4.1 內建模組

Node.js 提供許多內建模組,例如:

const fs = require('fs'); // 檔案系統
const path = require('path'); // 路徑處理
const os = require('os'); // 作業系統資訊

4.2 NPM 套件管理

安裝 Express 框架:

npm install express

5. Express 入門

5.1 建立簡單的 Express 伺服器

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

app.get('/', (req, res) => {
res.send('Hello, Express!');
});

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

6. 讀取與寫入檔案

6.1 讀取檔案

const fs = require('fs');

fs.readFile('example.txt', 'utf8', (err, data) => {
if (err) throw err;
console.log(data);
});

6.2 寫入檔案

fs.writeFile('output.txt', 'Hello, Node.js!', (err) => {
if (err) throw err;
console.log('File has been saved!');
});

7. 連接 MongoDB 資料庫

7.1 安裝 MongoDB 驅動

npm install mongoose

7.2 連接 MongoDB

const mongoose = require('mongoose');

mongoose.connect('mongodb://localhost:27017/testDB', { useNewUrlParser: true, useUnifiedTopology: true })
.then(() => console.log('Connected to MongoDB'))
.catch(err => console.error(err));

8. 部署 Node.js 應用

8.1 使用 PM2 管理應用

安裝 PM2:

npm install -g pm2

啟動應用:

pm2 start app.js

8.2 使用 Docker 部署

建立 Dockerfile

FROM node:14
WORKDIR /app
COPY package.json .
RUN npm install
COPY . .
CMD ["node", "app.js"]
EXPOSE 3000

9. 結論

透過這篇入門筆記,初學者可以快速掌握 Node.js 的基礎概念與實作技巧,包含 HTTP 伺服器、檔案操作、資料庫連接等。建議進一步學習異步程式設計、RESTful API、WebSocket 以及雲端部署技術,以提升開發能力。