跳至主要内容

React useMemo 與 useCallback 差異介紹與入門教學 | 學習筆記

· 閱讀時間約 4 分鐘
kdchang

在使用 React hooks 進行開發時,useMemouseCallback 是兩個常被提及的性能優化工具。它們都屬於 記憶化(memoization) 技術,用來避免不必要的重算與重渲染。然而,很多初學者在理解這兩者的用途與差異時常感到困惑。這篇文章將從概念出發,並搭配實際範例,幫助你掌握 useMemouseCallback 的核心用途與實作方式。


一、共通點:記憶化

React 在每次組件渲染時,預設會重新執行所有函式與表達式。當某些值(如計算結果、函式)在依賴未改變的情況下不需要重新產生,我們可以利用記憶化來優化效能。

這就是 useMemouseCallback 的主要功能:根據依賴陣列(dependency array)決定是否重建值或函式


二、差異概念總覽

Hook主要用途回傳內容使用時機
useMemo記憶「計算結果」任意值計算過程昂貴,避免重複運算
useCallback記憶「函式定義」函式傳遞函式給子元件,避免不必要的 re-render

換句話說:

  • useMemo(fn, deps)const value = memoized(fn)
  • useCallback(fn, deps)const callback = memoized(() => fn)

三、useMemo 範例

假設我們有一個需要進行繁重計算的函式,例如統計某個資料集合中的數值:

import React, { useState, useMemo } from 'react';

function slowFunction(num) {
console.log('Running slow function');
let result = 0;
for (let i = 0; i < 1e7; i++) {
result += num;
}
return result;
}

function ExpensiveComponent() {
const [count, setCount] = useState(0);
const [input, setInput] = useState(1);

const computedValue = useMemo(() => {
return slowFunction(input);
}, [input]);

return (
<div>
<h2>Expensive Calculation</h2>
<p>計算結果:{computedValue}</p>
<input
type="number"
value={input}
onChange={e => setInput(Number(e.target.value))}
/>
<button onClick={() => setCount(c => c + 1)}>重新渲染 ({count})</button>
</div>
);
}

在這個例子中,若不使用 useMemo,只要任何 state 改變(例如點擊按鈕改變 count),整個組件都會重新執行 slowFunction,導致效能問題。透過 useMemo,只有 input 改變時才會重新計算,其他情況會重複使用上次的計算結果。


四、useCallback 範例

有時候我們會將函式作為 props 傳遞給子元件。如果每次重新 render 都產生新的函式實例,會導致子元件誤以為 props 改變,而重新渲染。這時就可以用 useCallback

import React, { useState, useCallback, memo } from 'react';

const ChildButton = memo(({ onClick }) => {
console.log('ChildButton rendered');
return <button onClick={onClick}>點我</button>;
});

function ParentComponent() {
const [count, setCount] = useState(0);
const [other, setOther] = useState(0);

const handleClick = useCallback(() => {
setCount(c => c + 1);
}, []);

return (
<div>
<h2>useCallback Demo</h2>
<p>Count: {count}</p>
<ChildButton onClick={handleClick} />
<button onClick={() => setOther(o => o + 1)}>改變其他狀態</button>
</div>
);
}

在這個範例中,ChildButton 是經過 memo 包裹的元件,只有在 props.onClick 改變時才會重新渲染。使用 useCallback 確保 handleClick 函式在 [](無依賴)下只會創建一次,即使 other 改變,ChildButton 也不會重新渲染。


五、常見錯誤與注意事項

  1. 過度使用會反效果
    useMemouseCallback 本身也有記憶成本,不建議過度使用。只有在你確定函式或運算昂貴,或造成子元件重 render 才需要用。

  2. 依賴陣列要正確
    記得將函式中引用的變數正確加入依賴陣列中,否則會造成記憶結果與預期不符。

  3. 搭配 React.memo 效果更明顯
    useCallback 通常與 memoPureComponent 搭配,否則即使函式地址一樣,也無法避免重 render。


六、總結

當你在開發中遇到效能瓶頸或元件不必要地重複渲染時,才是使用 useMemouseCallback 的好時機。舉例來說:

  • 在表格過濾、排序等涉及大量資料處理的畫面中,可以用 useMemo 優化計算。
  • 在表單中將函式傳遞給 input 元件時,使用 useCallback 可避免整個表單重 render。

記住一個原則:不要為了使用 hook 而使用,而是根據實際效能需求進行優化。如果你的應用很小或尚未遇到效能問題,先專注於撰寫可讀性高、邏輯清楚的程式碼,這才是最重要的。

參考文件

  1. 如何優化 React 元件的渲染效能,並避免渲染陷阱

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

· 閱讀時間約 4 分鐘
kdchang

前言:什麼是 Angular?

Angular 是由 Google 支持與維護的前端框架,採用 TypeScript 開發,用於建構單頁應用程式(SPA)。Angular 提供了強大的資料繫結(Data Binding)、組件化設計(Component-based Architecture)、模組化管理(Modules)、路由(Routing)等功能,適合用來開發大型且可維護性的應用。

Angular 與其他前端框架(如 React、Vue)最大的不同在於它是一個完整框架,除了 UI 呈現外,也包含完整的路由HTTP 通訊表單管理等功能。


Angular 基本架構

  • 模組(Module):用來組織應用程式的功能區塊,至少有一個根模組 AppModule
  • 組件(Component):UI 的基本單位,每個組件包含 HTML 模板、CSS 樣式與 TypeScript 類別。
  • 服務(Service):用來封裝業務邏輯和資料存取,可被組件注入使用。
  • 路由(Router):用於處理頁面間的切換。

開發環境準備

  1. 安裝 Node.js(建議版本 16+)

  2. 安裝 Angular CLI(命令列工具):

    npm install -g @angular/cli
  3. 建立新專案:

    ng new my-angular-app

    期間會詢問是否要加入路由、使用 CSS 或 SCSS 等,可依需求選擇。

  4. 進入專案資料夾:

    cd my-angular-app
  5. 啟動開發伺服器:

    ng serve
  6. 開啟瀏覽器,輸入 http://localhost:4200 即可看到預設的 Angular 首頁。


實際範例:簡單的計數器應用

以下將示範如何用 Angular 建立一個簡單的計數器,包含按鈕點擊事件與資料顯示。

Step 1: 建立計數器組件

使用 Angular CLI 建立組件:

ng generate component counter

這會產生一個新的資料夾 src/app/counter,包含四個檔案:

  • counter.component.ts(TypeScript 程式碼)
  • counter.component.html(HTML 模板)
  • counter.component.css(樣式)
  • counter.component.spec.ts(測試)

Step 2: 編輯組件程式碼

counter.component.ts

import { Component } from "@angular/core";

@Component({
selector: "app-counter",
templateUrl: "./counter.component.html",
styleUrls: ["./counter.component.css"],
})
export class CounterComponent {
count = 0;

increment() {
this.count++;
}

decrement() {
this.count--;
}

reset() {
this.count = 0;
}
}

這裡我們定義了一個 count 屬性,以及三個方法:increment()decrement()reset(),用來操作計數值。

Step 3: 編輯 HTML 模板

counter.component.html

<div style="text-align: center;">
<h2>簡單計數器</h2>
<p>目前數值:{{ count }}</p>

<button (click)="increment()">增加</button>
<button (click)="decrement()">減少</button>
<button (click)="reset()">重設</button>
</div>

這裡使用 Angular 的資料繫結語法 {{ count }} 顯示目前的計數值,並用 (click) 綁定按鈕點擊事件,呼叫組件方法。

Step 4: 將組件放入主畫面

打開 src/app/app.component.html,將預設內容替換成:

<app-counter></app-counter>

這樣就會在首頁顯示剛剛建立的計數器組件。


Angular 的重要概念說明

  1. 資料繫結(Data Binding)

    • 插值表達式 {{ }}:用來顯示組件的變數。
    • 事件繫結 (event):如 (click) 綁定按鈕事件。
    • 雙向繫結 [(ngModel)]:常用於表單元素,需引入 FormsModule
  2. 組件生命週期(Lifecycle Hooks) Angular 組件有多個生命週期方法,如 ngOnInit()(初始化時呼叫)、ngOnDestroy()(銷毀時呼叫),可用於在特定時機執行程式碼。

  3. 依賴注入(Dependency Injection, DI) Angular 內建 DI 系統,可以將服務注入組件,提高模組化與可測試性。


總結

  • Angular 是功能完整的前端框架,適合開發大型 SPA。
  • 透過 CLI 工具,可以快速建立專案與組件。
  • 基本組件包含 TypeScript 類別與 HTML 模板,藉由資料繫結和事件綁定實現互動。
  • 了解 Angular 核心概念如模組、組件、服務與路由,能幫助設計良好的應用結構。

如果想更深入學習,可以參考官方文件 Angular 官方網站,或是搭配範例專案與實作練習,進一步掌握 Angular 的強大功能。

金流使用 Hash 原因入門教學筆記 | 學習筆記

· 閱讀時間約 5 分鐘
kdchang

在金流(第三方支付或自建金流)系統中使用 Hash(雜湊) 的原因,主要是為了安全性防止資料被竄改。以下是更詳細的解釋:


一、為什麼金流需要 Hash?

金流交易涉及敏感資料(如金額、訂單編號、使用者資訊、驗證參數等),若這些資料在傳輸過程中被竄改,將造成:

  • 金額錯誤(被調整為更小金額)
  • 偽造付款成功通知
  • 使用者資料外洩
  • 金流詐騙行為

因此,金流平台與商家之間需要一種方式來驗證資料完整性與來源,這就是使用 Hash 的目的。


二、Hash 的功能與好處

1. 防止資料被竄改

將參數與商家的金鑰(如 HashKey, HashIV, Secret) 一起進行加密產生 Hash,當接收到資料時重新運算比對 Hash 是否一致,可判斷資料是否遭到中間人修改。

2. 驗證資料來源(防偽)

因為 Hash 中包含商家密鑰,只有商家與金流平台知道,第三方無法偽造相同的 Hash 值。

3. 簽章比對(防止偽造)

如同電子簽章,確保伺服器回傳的付款成功訊息未被偽造。


三、常見應用場景

  1. 交易請求(Payment Request)

    • 商家送出訂單資訊與 Hash 到金流平台。
    • 金流平台驗證 Hash 是否正確。
  2. 金流回傳通知(Callback)

    • 金流平台回傳付款成功訊息與 Hash。
    • 商家伺服器驗證 Hash 是否正確,確保通知未被偽造。

四、常見演算法

常用於金流驗證的 Hash 演算法包括:

  • SHA-256(常見於 Line Pay、藍新金流)
  • SHA-1 / MD5(部分舊系統使用,不建議,因為已知有碰撞風險)

五、範例(以 SHA-256 為例)

import hashlib
import urllib.parse

def generate_hash(params, hash_key, hash_iv):
# 1. 排序參數
sorted_params = sorted(params.items())

# 2. 組合參數字串
query = urllib.parse.urlencode(sorted_params)

# 3. 加入 HashKey 和 HashIV
raw = f"HashKey={hash_key}&{query}&HashIV={hash_iv}"

# 4. URL encode 並轉為小寫
encoded = urllib.parse.quote_plus(raw).lower()

# 5. SHA-256 雜湊後轉大寫
hash_value = hashlib.sha256(encoded.encode('utf-8')).hexdigest().upper()

return hash_value

六、重點摘要

  • 什麼是 Hash?

    • 一種不可逆的單向編碼函數。
    • 輸入內容不論長短,輸出皆為固定長度的字串(雜湊值)。
  • Hash 的特性

    • 不可逆性(無法由輸出推回輸入)。
    • 輸入微幅變動會產生截然不同的結果。
    • 相同輸入會產生相同輸出(穩定性)。
  • 金流為何需要 Hash?

    1. 資料驗證:確保交易資料在傳送過程未遭竄改。
    2. 身份驗證:用來確認請求是否來自可信來源(如商家端或金流平台)。
    3. 防止偽造:Hash 搭配密鑰可防止惡意第三方偽造交易請求。
    4. 簽章與紀錄:用於產生交易紀錄摘要,利於日後比對查核。
  • 常見應用場景

    • 第三方金流(如:藍新、綠界、Line Pay)API 請求驗證。
    • 商店端對回傳交易結果進行驗證(例如 callback 處理)。
    • 加密交易參數避免資料外洩。

實際範例

以下以台灣常見的金流服務商 綠界科技(ECPay) 為例,說明 Hash 如何在金流流程中使用。

📌 模擬付款請求流程中的 Hash 機制

假設我們要透過金流平台建立一筆訂單,商家需呼叫綠界的 API 送出交易請求,為確保資料未被竄改,綠界要求每筆請求需附帶經 Hash 加密的驗證碼(CheckMacValue)。

1. 建立交易資料

MerchantID=2000132
MerchantTradeNo=KD123456789
MerchantTradeDate=2025/06/12 15:00:00
PaymentType=aio
TotalAmount=1000
TradeDesc=測試交易
ItemName=測試商品1件
ReturnURL=https://www.kdchang.me/callback
ChoosePayment=ALL

2. 組合字串(需加上 HashKey 與 HashIV)

HashKey=5294y06JbISpM5x9
HashIV=v77hoKGq4kWxNNIS

組合前格式如下(依照參數 ASCII 排序):
HashKey=5294y06JbISpM5x9&ChoosePayment=ALL&ItemName=測試商品1件&MerchantID=2000132&MerchantTradeDate=2025/06/12 15:00:00&MerchantTradeNo=KD123456789&PaymentType=aio&ReturnURL=https://www.kdchang.me/callback&TotalAmount=1000&TradeDesc=測試交易&HashIV=v77hoKGq4kWxNNIS

3. 進行 URL Encode 與轉成小寫,再做 Hash(通常使用 SHA256)

經過 SHA256 計算後會得到一段長度為 64 字元的十六進位字串:

CheckMacValue=5F8BCE79E9D2748F443D751B34EC5085EDB8C52265521FC42D6F807F208F47D2

此值將作為驗證碼隨交易資料一併傳送至金流平台。

4. 綠界端驗證流程

綠界收到資料後,會根據相同的規則計算一次 Hash 值並與商家提供的 CheckMacValue 比對,若一致,才表示資料未遭竄改,交易請求才會被接受。


延伸說明:為什麼不用密碼明文?

有些人可能會問:「為何不直接傳送密碼或 token 來驗證身分?」原因在於:

  • 密碼若明文傳輸容易被攔截。
  • 即使是 HTTPS 傳輸,也無法防止伺服器端遭駭後資料外洩。
  • Hash 機制能將敏感資訊加密為單向摘要,即使外洩也難以還原原始資料。

若搭配 時間戳記一次性 tokenHMAC(含密鑰的 Hash),可大幅提升安全性。

七、總結

金流使用 Hash 的核心目的是「確保資料未被篡改並驗證來源合法性」。它就像資料的「指紋」,能保證交易資訊的完整性與安全性。若缺乏這種驗證機制,金流系統就容易遭受攻擊或詐騙,對商家與消費者都造成風險。

因此,不論是在串接金流 API、處理回傳通知或比對訂單資訊時,妥善處理 Hash 的生成與比對,是金流串接不可忽略的重要環節。

LINE Pay API 入門教學筆記 | 學習筆記

· 閱讀時間約 4 分鐘
kdchang

一、前言

在電子商務與線上交易日益普及的今天,提供穩定又方便的金流服務已成為網站與應用程式不可或缺的一環。LINE Pay 是由 LINE Corporation 推出的行動支付平台,除了在線上與實體店面支援消費者付款外,也提供開發者 API 介面來整合第三方商務服務,使開發者可以在網站或應用程式中無縫串接付款功能。

本篇教學筆記將帶你了解 LINE Pay API 的基本觀念、運作流程,以及如何透過簡單的實作實現付款功能,協助你更快進入金流整合領域。


二、重點摘要

  • LINE Pay 是 LINE 提供的電子支付平台,可整合至網站或 App。

  • LINE Pay API 採用 RESTful 設計,以 HTTPS POST/GET 傳遞 JSON 格式。

  • API 主要分為以下幾個步驟:

    • Request:建立付款請求
    • Redirect:用戶跳轉至 LINE Pay 完成付款
    • Confirm:付款成功後確認交易
  • 使用前需在 LINE Pay 商戶後台 申請帳戶與 Channel。

  • API 使用需要 Channel ID、Channel Secret Key,以及測試或正式的 LINE Pay API Endpoint。

  • 所有請求需附加簽章(Signature Header)以進行授權與安全驗證。

  • 支援多種付款方式(LINE Pay 錢包、信用卡、優惠券等)。


三、整體交易流程圖

  1. 使用者在你的網站點擊「立即付款」
  2. 後端呼叫 request API 建立交易
  3. 將使用者導向 LINE Pay 的付款畫面
  4. 用戶完成付款後,LINE 會導回你設定的 redirect URL
  5. 你後端呼叫 confirm API 完成交易驗證

四、準備工作

  1. 註冊商戶帳號:至 LINE Pay Developer Center 申請開發者帳號與商戶帳戶。
  2. 建立 Channel:建立 LINE Pay Channel 取得 Channel IDChannel Secret Key
  3. 設定 Redirect URL:設置付款成功/失敗的回傳網址。
  4. 測試環境:LINE Pay 提供 Sandbox 環境(沙盒),可模擬交易流程。

五、實際範例(使用 Python Flask 示範)

1. 建立付款請求:/create_payment

import requests, json, time, hashlib, hmac
from flask import Flask, redirect, request

app = Flask(__name__)

CHANNEL_ID = '你的Channel ID'
CHANNEL_SECRET = '你的Secret Key'
LINE_PAY_ENDPOINT = 'https://sandbox-api-pay.line.me'
CONFIRM_URL = 'https://你的網站.com/confirm_payment'
RETURN_HOST = 'https://你的網站.com'

def generate_signature(uri, body, nonce, secret):
message = secret.encode('utf-8')
raw = f"{secret}{uri}{json.dumps(body, separators=(',', ':'))}{nonce}"
return hmac.new(message, raw.encode('utf-8'), hashlib.sha256).hexdigest()

@app.route('/create_payment')
def create_payment():
uri = '/v3/payments/request'
url = f"{LINE_PAY_ENDPOINT}{uri}"
nonce = str(int(time.time() * 1000))
headers = {
'Content-Type': 'application/json',
'X-LINE-ChannelId': CHANNEL_ID,
'X-LINE-Authorization-Nonce': nonce,
'X-LINE-Authorization': generate_signature(uri, {
"amount": 100,
"currency": "TWD",
"orderId": "ORDER123456",
"packages": [{
"id": "1",
"amount": 100,
"name": "商品",
"products": [{"name": "測試商品", "quantity": 1, "price": 100}]
}],
"redirectUrls": {
"confirmUrl": CONFIRM_URL,
"cancelUrl": f"{RETURN_HOST}/cancel"
}
}, nonce, CHANNEL_SECRET)
}

body = {
"amount": 100,
"currency": "TWD",
"orderId": "ORDER123456",
"packages": [{
"id": "1",
"amount": 100,
"name": "商品",
"products": [{"name": "測試商品", "quantity": 1, "price": 100}]
}],
"redirectUrls": {
"confirmUrl": CONFIRM_URL,
"cancelUrl": f"{RETURN_HOST}/cancel"
}
}

res = requests.post(url, headers=headers, json=body)
res_data = res.json()
payment_url = res_data['info']['paymentUrl']['web']
return redirect(payment_url)

2. 完成付款後確認交易:/confirm_payment

@app.route('/confirm_payment')
def confirm_payment():
transaction_id = request.args.get('transactionId')
uri = f"/v3/payments/{transaction_id}/confirm"
url = f"{LINE_PAY_ENDPOINT}{uri}"
nonce = str(int(time.time() * 1000))
body = {
"amount": 100,
"currency": "TWD"
}
headers = {
'Content-Type': 'application/json',
'X-LINE-ChannelId': CHANNEL_ID,
'X-LINE-Authorization-Nonce': nonce,
'X-LINE-Authorization': generate_signature(uri, body, nonce, CHANNEL_SECRET)
}
res = requests.post(url, headers=headers, json=body)
return res.json()

六、注意事項

  • 所有請求必須使用 HTTPS。
  • 金額與幣別需與建立交易時一致,否則確認交易會失敗。
  • 建議使用 UUID 或時間戳記作為 orderId 以避免重複。
  • 正式環境與 Sandbox 的 API Endpoint 不同,測試時請使用 sandbox 網址。

七、總結

LINE Pay API 提供了強大的付款整合能力,對於電商網站、小型應用或自有服務都有極大的幫助。透過良好的 API 設計與嚴謹的安全驗證,開發者可以快速、安全地建置付款機制。建議開發者先熟悉 sandbox 測試流程,再導入正式環境,以確保交易安全與穩定性。

若你有更多需求,例如分期付款、自動收款、退款等,也可進一步參考官方完整文件:LINE Pay API 技術文件

Vue ref 與 reactive 入門教學筆記 | 學習筆記

· 閱讀時間約 2 分鐘
kdchang

前言

在 Vue 3 中,refreactive 都是用來創建響應式資料的工具,但它們適用的情境略有不同。以下是清楚的比較與實際使用範例,幫助你理解什麼時候該用哪一個。


ref 使用情境

適用於:

  • 原始資料型別:如 NumberStringBooleanDate 等。
  • 你只需要追蹤單一值。
  • 當你需要某個變數傳遞到 <template> 或函式中。

語法:

import { ref } from 'vue'

const count = ref(0)
count.value++ // 需要用 .value 訪問或修改

範例:

<script setup>
import { ref } from 'vue'

const message = ref('Hello')
const count = ref(0)

const updateMessage = () => {
message.value = 'Hi Vue 3'
}
</script>

reactive 使用情境

適用於:

  • 物件或陣列:需要操作多個屬性的資料結構。
  • 多層巢狀資料或你希望所有屬性都具有響應性。
  • 表單資料、設定物件等複雜狀態管理。

語法:

import { reactive } from 'vue'

const user = reactive({
name: 'Daniel',
age: 30
})

user.age++ // 直接修改即可,無需 .value

範例:

<script setup>
import { reactive } from 'vue'

const form = reactive({
username: '',
email: '',
isSubscribed: false
})

const submitForm = () => {
console.log(form.username, form.email, form.isSubscribed)
}
</script>

⚠️ ref 包物件 vs reactive

雖然你也可以這樣用:

const user = ref({ name: 'Daniel', age: 30 })

但要存取時就需要:

user.value.name = 'John'

reactive 則可以直接操作:

user.name = 'John'

所以若你有物件資料,通常選擇 reactive 更直覺。


建議使用方式整理:

類型建議使用方式
單一變數(數字、字串)ref
多欄位表單資料reactive
陣列、物件、巢狀結構reactive
要被 watchcomputed 的原始值ref

JavaScript 模組系統:CommonJS 與 AMD 入門教學 | 學習筆記

· 閱讀時間約 4 分鐘
kdchang

前言

在 JavaScript 早期,所有程式碼通常寫在單一文件中,這樣的方式在小型專案中或許可行,但當應用程式變得更大、更複雜時,這種結構會導致管理困難。因此,模組化的概念被引入,允許開發者將程式碼拆分成可重複使用的獨立部分,提高可維護性與擴展性。

在 ES6 標準推出之前,JavaScript 主要依賴 CommonJS(CJS)Asynchronous Module Definition(AMD) 來實現模組化。這兩種模組系統有不同的設計理念與應用場景,以下將詳細介紹其特性與實作方式。


1. CommonJS(CJS)—— Node.js 的標準模組系統

概述

CommonJSNode.js 所採用,主要用於伺服器端開發。它的核心概念是 同步載入(Synchronous Loading),這意味著模組在執行時會逐步載入,而不是並行載入。

CommonJS 主要透過 require() 來載入模組,並使用 module.exportsexports 來匯出模組內容。

CommonJS 語法

(1) 定義模組(匯出)

// math.js
const pi = 3.14159;

function add(a, b) {
return a + b;
}

function subtract(a, b) {
return a - b;
}

// 匯出模組
module.exports = {
pi,
add,
subtract
};

(2) 使用模組(載入)

// main.js
const math = require('./math');

console.log(math.pi); // 3.14159
console.log(math.add(10, 5)); // 15
console.log(math.subtract(20, 8)); // 12

CommonJS 特點

優點: 適用於伺服器端(Node.js),可輕鬆管理模組與依賴。
簡單直覺的 require()module.exports 語法
支援循環依賴(Circular Dependencies),當兩個模組互相依賴時,仍能正確解析。

缺點: 同步載入(Synchronous Loading),不適合瀏覽器端,因為會阻塞執行緒,影響頁面效能。
不支援瀏覽器環境,需使用 Webpack 或 Browserify 來轉換為瀏覽器可用的程式碼。


2. AMD(Asynchronous Module Definition)—— 適用於瀏覽器的模組系統

概述

AMD 是專為 瀏覽器環境 設計的模組系統,解決了 CommonJS 無法在前端環境直接運作的問題。AMD 的關鍵特點是 非同步載入(Asynchronous Loading),允許模組在需要時才載入,避免影響頁面效能。

AMD 主要使用 define() 來定義模組,require() 來載入模組。

AMD 語法

(1) 定義模組(匯出)

// math.js
define([], function () {
const pi = 3.14159;

function add(a, b) {
return a + b;
}

function subtract(a, b) {
return a - b;
}

return {
pi,
add,
subtract
};
});

(2) 使用模組(載入)

// main.js
require(['math'], function (math) {
console.log(math.pi); // 3.14159
console.log(math.add(5, 6)); // 11
});

AMD 特點

優點: 適用於瀏覽器環境,支援非同步載入,提高效能。
使用 define()require() 來管理模組,能夠載入多個依賴。
非同步執行,適合大型應用程式,減少載入時間。

缺點: 語法較繁瑣,比 CommonJS 需要更多設定。
需要 RequireJS 來執行,瀏覽器無法直接支援 AMD。


3. CommonJS vs AMD vs ES Modules(ESM)

特性CommonJS(CJS)AMDES Modules(ESM)
適用環境Node.js瀏覽器瀏覽器 & Node.js
載入方式require()require()import/export
同步/非同步同步(Synchronous)非同步(Asynchronous)靜態解析(Static)
優勢簡單易用,適合伺服器端適用瀏覽器,非同步載入現代標準,支援 Tree Shaking
限制不適合瀏覽器需要 RequireJS需要 ES6 瀏覽器或 Node.js 12+

4. CommonJS 與 AMD 的使用時機

  • 當開發伺服器端應用程式時,建議使用 CommonJS,因為它與 Node.js 相容性最佳。
  • 當開發前端應用時,AMD 是一種選擇,但目前更推薦使用 ES Modules(ESM)
  • 現代 JavaScript 建議使用 ES Modules(import/export),因為它已經成為標準,並且同時支援瀏覽器與 Node.js 環境。

5. 結論

在 JavaScript 模組化的歷史發展中,CommonJS 被廣泛用於 伺服器端,而 AMD 則主要針對 瀏覽器環境 設計。隨著 ES6 的 ES Modules(ESM)標準化,許多開發者已經轉向 ESM,因為它在語法上更直覺,並且可以同時適用於前端與後端。

雖然 CommonJS 和 AMD 仍然在某些專案中使用,但未來趨勢將逐漸轉向 ES Modules。因此,對於新專案,建議 優先使用 ES Modules(import/export),而對於舊專案或特定環境(如 Node.js 早期版本),仍可能需要使用 CommonJS 或 AMD。

JavaScript 模組(Module)入門教學筆記 | 學習筆記

· 閱讀時間約 4 分鐘
kdchang

1. 什麼是 JavaScript 模組?

JavaScript 模組(Module)是一種將程式碼拆分成多個獨立文件,並在不同文件間共享和管理程式碼的方式。透過模組化的設計,可以讓程式碼更具結構性、可讀性與可維護性。

在 ES6(ECMAScript 2015)之前,JavaScript 主要透過 IIFE(立即執行函式)、CommonJS 或 AMD 來模組化程式碼。而 ES6 之後,JavaScript 原生支援 ES Modules(ESM),提供 importexport 來管理模組。


2. 為什麼需要模組?

  1. 避免全域變數污染
    • 模組能夠封裝變數,避免不同程式碼區塊互相影響。
  2. 提高可維護性
    • 讓程式碼結構更清晰,拆分不同的功能至獨立文件中。
  3. 支援程式碼重用
    • 可在多個專案中共享相同的模組,避免重複開發。
  4. 支援延遲載入(Lazy Loading)
    • 透過動態 import(),按需載入模組,提高效能。

3. ES6 模組語法

在 ES6 中,我們主要使用 exportimport 來定義和載入模組。

(1) export 的使用

命名匯出(Named Export)

// math.js
export const pi = 3.14159;
export function add(a, b) {
return a + b;
}
export function subtract(a, b) {
return a - b;
}

預設匯出(Default Export)

// greeting.js
export default function sayHello(name) {
return `Hello, ${name}!`;
}

(2) import 的使用

匯入命名匯出

// main.js
import { pi, add, subtract } from "./math.js";

console.log(pi); // 3.14159
console.log(add(5, 3)); // 8
console.log(subtract(10, 4)); // 6

匯入預設匯出

// main.js
import sayHello from "./greeting.js";

console.log(sayHello("Alice")); // "Hello, Alice!"

匯入所有模組內容

// main.js
import * as math from "./math.js";

console.log(math.pi); // 3.14159
console.log(math.add(2, 3)); // 5

使用 as 重新命名

import { add as sum, subtract as minus } from "./math.js";

console.log(sum(10, 5)); // 15
console.log(minus(10, 5)); // 5

4. ES 模組的特性

  1. 靜態解析(Static Analysis)

    • importexport 必須在頂層作用域,不能在條件語句或函式內部。
    • 在編譯時(compile time)解析模組,而不是執行時(runtime)。
  2. 模組作用域

    • 每個模組都有自己的作用域,變數不會污染全域作用域。
  3. 自動使用嚴格模式(Strict Mode)

    • ES6 模組內部自動啟用 "use strict",無需手動指定。

5. 動態載入模組

有時候我們希望在特定條件下載入模組,而不是在程式開始時就載入所有模組。這時可以使用 import() 來動態載入。

if (true) {
import("./math.js").then((math) => {
console.log(math.add(5, 10)); // 15
});
}
  • import() 回傳一個 Promise,當模組載入完成後執行回調函式。
  • 這種方式適合懶加載(Lazy Loading)與條件性載入。

6. varlet 在模組中的行為

在模組內,變數 var 仍然會被提升(Hoisting),但 letconst 具有區塊作用域。

// module.js
var globalVar = "I am global";
let localVar = "I am local";
// main.js
import "./module.js";

console.log(globalVar); // "I am global" (因為 var 會提升到全域)
console.log(localVar); // ReferenceError: localVar is not defined

7. 在瀏覽器與 Node.js 環境使用 ES 模組

(1) 瀏覽器

在 HTML 文件中,使用 <script type="module"> 來載入 ES6 模組。

<script type="module">
import { add } from "./math.js";
console.log(add(10, 5));
</script>

(2) Node.js

Node.js 14+ 版本支援 ES 模組,但需要:

  • 檔案副檔名改為 .mjs
  • package.json 設定 "type": "module"
{
"type": "module"
}
// math.mjs
export function multiply(a, b) {
return a * b;
}
// main.mjs
import { multiply } from "./math.mjs";
console.log(multiply(4, 5)); // 20

8. 模組引入方式整理

環境引入方式
瀏覽器(ESM)<script type="module">
Node.js(ESM)import { foo } from './module.mjs'
Node.js(CommonJS)const foo = require('./module.js')
動態載入(Lazy Load)import('./module.js').then(...)
重新命名import { foo as newFoo } from './module.js'
匯入所有內容import * as mod from './module.js'

9. 結論

  1. ES 模組是 JavaScript 原生模組系統,使用 importexport 來管理程式碼。
  2. 模組有助於提升可讀性與可維護性,避免全域變數污染。
  3. 動態載入(import())可以優化效能,適合延遲載入模組。
  4. 瀏覽器與 Node.js 都支援 ES6 模組,但 Node.js 需要 .mjspackage.json 設定 "type": "module"
  5. 模組可以透過不同方式引入,根據環境選擇適合的方法。

掌握 JavaScript 模組的概念,能夠讓你更有效地開發與維護大型專案。

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

· 閱讀時間約 4 分鐘
kdchang

前言

在現代應用程式開發中,資料儲存已不再侷限於傳統的關聯式資料庫(如 MySQL、PostgreSQL)。特別是在處理非結構化資料、需要高延展性或頻繁 schema 變動的應用場景中,NoSQL 資料庫逐漸成為主流選擇。

其中,MongoDB 是最受歡迎的 NoSQL 資料庫之一。它採用文件型(Document-Oriented)結構,使用 JSON 類型格式(實際為 BSON)儲存資料,讓開發者能更靈活地設計資料模型與操作資料。MongoDB 強調可擴展性、彈性資料結構與高效查詢能力,廣泛應用於 Web 開發、物聯網、大數據處理等領域。


重點摘要

  • MongoDB 是什麼?

    • 開源的 NoSQL 文件資料庫,使用 BSON 格式儲存資料。
    • 資料以「資料庫 → 集合(Collection)→ 文件(Document)」的層級組織。
    • 每個文件(Document)類似於 JSON 結構,支援巢狀資料與陣列。
  • 主要特性

    • 文件型資料儲存(更彈性且接近開發者熟悉的物件結構)
    • 無需預先定義 Schema,可動態變更欄位
    • 垂直與水平延展能力佳
    • 提供複寫與分片支援(Replica Set、Sharding)
    • 強大的查詢語言,支援索引、聚合、全文搜尋
  • 應用場景

    • RESTful API 後端儲存(如 Node.js + Express 專案)
    • 快速原型設計與資料模型測試
    • 高並發讀寫需求(例如留言板、商品評論系統)
    • 資料格式變動頻繁的場景(如 IoT 裝置紀錄)

安裝與啟動

1. 安裝 MongoDB(本機)

Mac 使用者(使用 Homebrew):

brew tap mongodb/brew
brew install mongodb-community@7.0
brew services start mongodb/brew/mongodb-community

Windows / Linux: 可前往 https://www.mongodb.com/try/download/community 下載對應版本。

2. 啟動 MongoDB

mongod

啟動成功後,預設會在 mongodb://localhost:27017 提供本地服務。

3. 開啟 Mongo Shell(或使用 MongoDB Compass GUI)

mongosh

進入後會看到互動式 shell 環境,開始操作你的資料庫。


MongoDB 基本操作(Shell 範例)

1. 建立 / 切換資料庫

use blog

2. 建立集合(Collection)與新增文件(Document)

db.posts.insertOne({
title: 'MongoDB 入門教學',
author: 'KD',
tags: ['database', 'nosql', 'mongodb'],
published: true,
created_at: new Date(),
});

插入文件時自動建立集合與資料庫。

3. 查詢文件

db.posts.find();
db.posts.find({ author: 'KD' });
db.posts.findOne({ published: true });

支援條件、邏輯查詢、排序、分頁等功能:

db.posts.find({ published: true }).sort({ created_at: -1 }).limit(5);

4. 更新文件

db.posts.updateOne({ title: 'MongoDB 入門教學' }, { $set: { published: false } });

支援 $set, $inc, $push, $unset 等更新操作符。

5. 刪除文件

db.posts.deleteOne({ title: 'MongoDB 入門教學' });

使用 Mongoose 操作(Node.js 範例)

在 Node.js 專案中,常使用 mongoose 封裝操作 MongoDB。

1. 安裝套件

npm install mongoose

2. 建立連線與定義模型

const mongoose = require('mongoose');

mongoose.connect('mongodb://localhost:27017/blog');

const postSchema = new mongoose.Schema({
title: String,
author: String,
tags: [String],
published: Boolean,
created_at: { type: Date, default: Date.now },
});

const Post = mongoose.model('Post', postSchema);

3. 實際使用範例

新增資料:

const newPost = new Post({
title: '用 Node.js 操作 MongoDB',
author: 'KD',
tags: ['nodejs', 'mongodb'],
published: true,
});

await newPost.save();

查詢資料:

const posts = await Post.find({ published: true }).limit(5);

更新資料:

await Post.updateOne({ title: '用 Node.js 操作 MongoDB' }, { published: false });

刪除資料:

await Post.deleteOne({ title: '用 Node.js 操作 MongoDB' });

聚合(Aggregation)入門

MongoDB 提供強大的 Aggregation Pipeline 功能,可進行統計、分組、轉換。

範例:統計作者貼文數量

db.posts.aggregate([{ $group: { _id: '$author', count: { $sum: 1 } } }, { $sort: { count: -1 } }]);

總結

MongoDB 以其彈性、易用與高延展性,成為許多現代應用的首選資料庫,特別是在快速開發、微服務架構或大數據處理場景中表現優異。透過簡單的 JSON 結構與強大的查詢能力,即使不熟 SQL 的開發者也能快速上手,打造穩定且具擴展性的資料儲存系統。

初學者可先從基本的增刪查改練習起,逐步熟悉資料結構與聚合操作,再延伸到使用 Mongoose 開發 REST API,或搭配 GraphQL、Next.js 等前後端整合工具,深入打造現代 Web 應用。

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

· 閱讀時間約 3 分鐘
kdchang

1. 簡介

Express.js 是一個基於 Node.js 的 Web 應用框架,提供簡潔且靈活的 API,適用於建立伺服器端應用程式。它可以用來開發 RESTful API、Web 應用或後端服務。

為什麼選擇 Express.js?

  • 輕量且易於學習
  • 擴展性高
  • 內建強大的中介軟體(Middleware)系統
  • 支援各種範本引擎(例如:EJS, Pug)

2. 安裝與專案初始化

安裝 Node.js

在開始使用 Express.js 之前,請先安裝 Node.js

初始化專案

建立一個新的專案資料夾,然後執行以下指令來初始化 Node.js 專案:

mkdir express-app
cd express-app
npm init -y

安裝 Express.js

npm install express

3. 建立第一個 Express 伺服器

建立 server.js 檔案,並加入以下程式碼:

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

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

app.listen(port, () => {
console.log(`Server is running at http://localhost:${port}`);
});

啟動伺服器

node server.js

然後在瀏覽器打開 http://localhost:3000/,應該可以看到 Hello, Express!

4. 中介軟體(Middleware)

Express 提供 Middleware,可用來處理請求與回應,例如:解析請求體、驗證請求等。

使用 express.json() 解析 JSON

app.use(express.json());

建立自訂中介軟體

app.use((req, res, next) => {
console.log(`${req.method} ${req.url}`);
next();
});

5. 路由(Routing)

Express 允許定義不同的 HTTP 方法對應不同的路由。

GET 路由

app.get('/api/users', (req, res) => {
res.json([{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]);
});

POST 路由

app.post('/api/users', (req, res) => {
const newUser = req.body;
res.status(201).json(newUser);
});

參數化路由

app.get('/api/users/:id', (req, res) => {
const userId = req.params.id;
res.json({ id: userId, name: `User ${userId}` });
});

6. 靜態檔案服務

Express 可用來提供靜態檔案,例如 HTML、CSS、JavaScript。

app.use(express.static('public'));

然後在 public/index.html 中放入 HTML,即可直接透過 http://localhost:3000/index.html 存取。

7. 錯誤處理

Express 提供錯誤處理中介軟體,可用來處理應用中的錯誤。

app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ message: 'Something went wrong!' });
});

8. 整合 MongoDB

可以使用 mongoose 來與 MongoDB 互動。

安裝 mongoose

npm install mongoose

連接 MongoDB

const mongoose = require('mongoose');

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

9. 部署 Express 應用

可以使用 PM2 來管理 Express 伺服器。

安裝 PM2

npm install -g pm2

啟動應用

pm2 start server.js --name express-app

10. 總結

透過這篇筆記,你已經學會:

  1. 安裝與初始化 Express.js
  2. 建立基本 Web 伺服器
  3. 使用中介軟體與路由
  4. 提供靜態檔案
  5. 錯誤處理
  6. 整合 MongoDB
  7. 部署 Express 應用

這些概念是 Express.js 開發的基礎,熟練後可以進一步學習 JWT 認證、WebSocket、GraphQL 等進階技術!

Pinia Setup Store 與 Option Store 比較介紹入門教學筆記

· 閱讀時間約 4 分鐘
kdchang

前言

Pinia 是 Vue 3 推薦的狀態管理工具,作為 Vuex 的繼任者,Pinia 以簡潔且直觀的 API 設計,為 Vue 生態系帶來更輕量且易於維護的解決方案。Pinia 提供兩種撰寫 store 的方式:Option Store 與 Setup Store。兩者皆支援響應式狀態管理,但在語法結構與彈性上有所不同。理解這兩種寫法的差異,對於新手以及想提升開發效率的工程師來說相當重要。


重點摘要

  • Option Store 是物件式語法,結構清晰,適合剛接觸 Pinia 或 Vuex 過渡的開發者。
  • Setup Store 採用 Composition API 語法,彈性更大,易於搭配 Vue 3 的其它 API 使用。
  • Option Store 通常使用 stategettersactions 三大屬性分隔管理狀態、計算屬性與方法。
  • Setup Store 透過 refreactive 直接定義響應式狀態,並以函式回傳公開 API,結構更自由。
  • Setup Store 更方便與 computedwatchasync/await 等 Composition API 功能整合。
  • Option Store 語法更接近 Vuex,學習門檻較低,但在複雜邏輯時不易拆分。
  • Setup Store 支援多 store 之間更靈活的組合與重用。

1. Option Store 範例

// stores/counterOption.js
import { defineStore } from 'pinia';

export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
}),
getters: {
doubleCount(state) {
return state.count * 2;
},
},
actions: {
increment() {
this.count++;
},
incrementBy(amount) {
this.count += amount;
},
},
});

特點說明

  • state 是一個函式,回傳物件,定義響應式資料。
  • getters 為基於 state 的計算屬性,類似 Vue 的 computed。
  • actions 用於定義操作狀態的方法,且可包含異步邏輯。
  • 語法簡單,概念清晰,適合快速上手。

2. Setup Store 範例

// stores/counterSetup.js
import { defineStore } from 'pinia';
import { ref, computed } from 'vue';

export const useCounterStore = defineStore('counter', () => {
const count = ref(0);

const doubleCount = computed(() => count.value * 2);

function increment() {
count.value++;
}

function incrementBy(amount) {
count.value += amount;
}

return {
count,
doubleCount,
increment,
incrementBy,
};
});

特點說明

  • 直接使用 Vue Composition API 的 refcomputed
  • 可自由使用所有 Vue Composition API 來封裝邏輯。
  • 方法與狀態同時定義在函式作用域內,結構更模組化。
  • 支援更複雜狀態邏輯與副作用控制。

3. 比較分析

項目Option StoreSetup Store
語法風格物件式,類似 Vuex函式式,基於 Composition API
學習曲線容易入門,結構清楚需要熟悉 Vue 3 Composition API
狀態管理透過 state 函式返回物件使用 refreactive 定義狀態
計算屬性透過 getters 定義使用 computed 定義
方法定義放在 actions直接函式內定義,結構更自由
邏輯拆分較不靈活,所有邏輯集中可拆分成多個函式模組,彈性更高
異步處理支援,寫法較直觀支援,且更方便搭配 async/await
重用性較弱易於組合與重用,搭配 Composition API 更佳
持久化插件支持兩者皆支持兩者皆支持
與 Vue 3 整合一般深度整合,充分發揮 Vue 3 特性

4. 什麼時候用哪種?

  • **初學者或 Vuex 使用者:**建議先使用 Option Store,語法熟悉、過渡平滑。
  • **專案中需大量組合邏輯、重用邏輯時:**Setup Store 更適合,也更符合 Vue 3 生態。
  • **大型專案或團隊合作:**Setup Store 可拆分細節更清楚,便於維護與測試。
  • **希望在 Store 中使用複雜副作用(例如 watch、watchEffect):**必須使用 Setup Store。

5. 總結與建議

Pinia 的 Option Store 與 Setup Store 各有優劣,根據團隊熟悉度、專案規模與需求選擇合適的寫法。掌握兩者能幫助你靈活應對不同開發場景。


補充:如何在專案中使用

  1. 安裝 Pinia:
npm install pinia
  1. main.js 初始化:
import { createApp } from 'vue';
import { createPinia } from 'pinia';
import App from './App.vue';

const app = createApp(App);
const pinia = createPinia();

app.use(pinia);
app.mount('#app');
  1. 直接在組件中使用 store:
<script setup>
import { useCounterStore } from '@/stores/counterSetup';

const counter = useCounterStore();

function increase() {
counter.increment();
}
</script>

<template>
<div>
<p>Count: {{ counter.count }}</p>
<p>Double: {{ counter.doubleCount }}</p>
<button @click="increase">增加</button>
</div>
</template>