跳至主要内容

React Render HTML 入門教學 | w3schools 學習筆記

· 閱讀時間約 2 分鐘
kdchang

前言

React 是一個專門用於構建使用者介面(UI)的 JavaScript 函式庫,其主要目標就是將 HTML 呈現在網頁中。透過 React 的核心函式 createRoot() 和方法 render(),開發者可以將 UI 元件渲染到指定的 HTML 元素中。本文將介紹 React 如何在網頁上渲染 HTML,包括核心函式與方法的用途、應用位置與語法示範,並介紹與 JSX 語法的結合使用方式。

重點摘要

  • React 透過 createRoot() 函式與 render() 方法將 HTML 渲染到網頁上。
  • createRoot() 用於指定要渲染的目標 HTML 元素。
  • render() 用於將 React 元件實際渲染到指定元素中。
  • 標準的 React 專案會在 public/index.html 中提供 <div id="root"> 作為渲染容器。
  • JSX 語法允許開發者在 JavaScript 中撰寫類似 HTML 的程式碼。
  • 根節點不必一定是 <div>,也不一定要命名為 root

實際範例

  1. 渲染一段簡單的段落:
const container = document.getElementById('root');
const root = ReactDOM.createRoot(container);
root.render(<p>Hello</p>);

上述程式碼會將 "Hello" 文字渲染到 HTML 文件中的:

<body>
<div id="root"></div>
</body>
  1. 使用 JSX 撰寫 HTML 表格並渲染:
const myelement = (
<table>
<tr>
<th>Name</th>
</tr>
<tr>
<td>John</td>
</tr>
<tr>
<td>Elsa</td>
</tr>
</table>
);

const container = document.getElementById('root');
const root = ReactDOM.createRoot(container);
root.render(myelement);

此段程式會顯示一個包含名稱的簡單表格,渲染至 root 節點中。

  1. 更換根節點的標籤與 id: 不一定要使用 <div id="root"> 作為渲染節點,可以自行命名:
<body>
<header id="sandy"></header>
</body>

對應的 React 程式碼如下:

const container = document.getElementById('sandy');
const root = ReactDOM.createRoot(container);
root.render(<p>Hallo</p>);

此範例將段落渲染至 header 元素。

總結

React 的渲染邏輯核心在於兩個部分:定義渲染目標與執行渲染動作。透過 createRoot() 指定 HTML 元素、render() 注入 React 元件,開發者可以將 UI 動態顯示在任何節點上。搭配 JSX 語法,不僅可以撰寫更具可讀性的 UI 結構,也讓程式碼維護更為便利。

參考文件

  1. React Render HTML

React 與 ES6 語法教學入門 | w3schools 學習筆記

· 閱讀時間約 3 分鐘
kdchang

前言

ES6(ECMAScript 2015)是 JavaScript 的第六版,於 2015 年發布,為 JavaScript 帶來了重大語法革新。React 作為現今最受歡迎的前端框架和函式庫之一,其核心設計與語法極度依賴 ES6 的各項功能。因此,學習 React 前,理解 ES6 的語法特性將大幅提升開發效率與理解深度。

本文將說明 React 常用的 ES6 特性,並透過簡明範例幫助你掌握其實作方式。

重點摘要

  • ES6 是 ECMAScript 第六版,又稱 ECMAScript 2015。

  • React 使用大量 ES6 語法,包括:

    • Class 類別
    • 箭頭函式
    • let、const 變數宣告
    • 陣列方法(如 .map)
    • 解構(Destructuring)
    • 模組系統(import/export)
    • 三元運算子(Ternary Operator)
    • 展開運算子(Spread Operator)

1. 類別與繼承(Classes & Inheritance)

ES6 引入了 class 關鍵字來定義類別:

class Car {
constructor(name) {
this.brand = name;
}
present() {
return 'I have a ' + this.brand;
}
}

const mycar = new Car('Ford');
console.log(mycar.present());

繼承使用 extends 關鍵字:

class Model extends Car {
constructor(name, model) {
super(name);
this.model = model;
}
show() {
return this.present() + ', it is a ' + this.model;
}
}

const mycar = new Model('Ford', 'Mustang');
console.log(mycar.show());

2. 箭頭函式(Arrow Functions)

簡化函式的寫法:

const hello = () => 'Hello World!';
const greet = (name) => `Hello ${name}`;

3. 變數宣告(let、const、var)

var x = 5; // 函式作用域
let y = 10; // 區塊作用域
const z = 15; // 常數,不可重新指派

const 宣告的是參考不可變:

const arr = [1, 2];
arr.push(3); // 可以修改內容

4. 陣列方法(Array.map)

在 React 中常用於渲染列表:

const fruits = ['apple', 'banana', 'orange'];
const listItems = fruits.map((fruit) => <p>{fruit}</p>);

5. 解構賦值(Destructuring)

解構陣列:

const vehicles = ['mustang', 'f-150', 'expedition'];
const [car, , suv] = vehicles;

解構函式回傳值:

function calc(a, b) {
return [a + b, a - b];
}

const [sum, diff] = calc(5, 3);

6. 展開運算子(Spread Operator)

複製或合併陣列與物件:

const numbers1 = [1, 2];
const numbers2 = [3, 4];
const all = [...numbers1, ...numbers2];

const car = { brand: 'Ford', color: 'red' };
const update = { color: 'blue' };
const updatedCar = { ...car, ...update };

7. 模組系統(Modules)

exportimport 用於模組化程式碼:

命名匯出:

// person.js
export const name = 'Jesse';
export const age = 40;
// 使用
import { name, age } from './person.js';

預設匯出:

// message.js
const message = () => 'Hello';
export default message;
import message from './message.js';

8. 三元運算子(Ternary Operator)

條件判斷簡化語法:

const isAuth = true;
isAuth ? renderApp() : renderLogin();

結語

ES6 為 JavaScript 帶來嶄新的語法與思維方式,也為 React 帶來強大的表達力與模組化能力。熟練掌握這些語法,將能讓你在開發 React 專案時更加順手、高效並撰寫出更具可維護性的程式碼。若你尚未熟悉這些語法,建議你從簡單的練習開始,搭配 React 實際開發經驗進行吸收與內化。

參考文件

  1. React ES6

React 入門教學 | w3schools 學習筆記

· 閱讀時間約 3 分鐘
kdchang

前言

在當代的前端開發中,React 是最具代表性的 JavaScript 函式庫之一。由 Facebook 軟體工程師 Jordan Walke 所開發,React 被廣泛應用於建構動態使用者介面,尤其適用於大型單頁應用(SPA, Single Page Application)。本篇文章將帶你認識 React 的核心概念、其背後的工作原理、使用前的基礎知識與發展歷史,並透過實例進行初步實作。


重點摘要

  1. 什麼是 React?

    • React 是一個前端 JavaScript 函式庫,用來建構使用者介面(UI)。
    • React 的別稱包括 React.js 與 ReactJS。
    • 它專注於「元件化思維」,每個 UI 元素都被視為一個可重複使用的元件(Component)。
  2. React 如何運作?

    • React 在記憶體中建立一個「虛擬 DOM」(Virtual DOM)。
    • 所有 DOM 操作會先發生在虛擬 DOM 中,再批次更新實體 DOM。
    • 這樣的差異化更新策略可提升效能,只變更必要的部分。
  3. React 的優勢

    • 高效能:避免不必要的 DOM 操作。
    • 組件化:提升程式碼的重用性與可維護性。
    • 單向資料流:資料流動清晰易懂。
    • 強大社群與生態圈:大量開源資源與工具支援。
  4. 使用 React 前的必要基礎

    • 熟悉 HTML 結構與語意標記。
    • 理解 CSS 排版與樣式應用。
    • 掌握 JavaScript 基本語法與函數觀念(如 ES6 語法、變數宣告、陣列方法等)。
  5. React 的發展歷程

    • 2011 年:React 首次應用於 Facebook 的新聞動態功能(Newsfeed)。
    • 2013 年 7 月:React 對外發布首個公開版本 0.3.0。
    • 2024 年 12 月:React 最新穩定版本為 19.0.0。

實際範例

以下是一個簡單的 React 程式碼範例,展示如何建立與渲染一個元件。

// 引入 React 與 ReactDOM 函式庫
import React from 'react';
import ReactDOM from 'react-dom/client';

// 建立一個簡單的元件
function Welcome() {
return <h1>你好,React 世界!</h1>;
}

// 在指定的 DOM 節點中渲染該元件
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Welcome />);

解說:

  1. import React from 'react'; 是使用 React 所必須的匯入語句。
  2. Welcome 是一個函數式元件(Functional Component),回傳的是 JSX 語法。
  3. ReactDOM.createRoot 創建一個 root 對象,負責將元件掛載至網頁的 #root DOM 節點。
  4. root.render(<Welcome />) 是將我們的元件渲染到網頁中。

總結

React 是一個現代網頁開發的重要工具,特別適合構建具互動性與模組化結構的網頁。透過虛擬 DOM 技術,React 讓介面更新更有效率,並提供清晰的元件架構設計。對於有基本前端知識的開發者而言,React 是進階的最佳入門選擇之一。建議你在學習 React 前,先打好 HTML、CSS 與 JavaScript 的基礎,將能更順利掌握 React 的概念與應用。

接著我們將學習 React 的 JSX 語法、狀態管理(useState)、事件處理、生命週期(useEffect)等核心概念,逐步建立自己的 React 專案。

參考文件

  1. React Custom Hooks

事件物件屬性 e.target 與 e.currentTarget 的差異與應用 | 學習筆記

· 閱讀時間約 4 分鐘
kdchang

前言

在網頁開發中,事件處理是前端工程師必須熟悉的基礎知識之一。當我們使用 JavaScript 或框架(如 Vue、React)監聽 DOM 事件時,瀏覽器會提供一個事件物件(event object)給監聽函式,透過它我們能取得觸發事件的相關資訊。而在事件物件中,最常被初學者混淆的兩個屬性就是 e.targete.currentTarget

這兩者的差別看似微小,但實際上與事件冒泡機制息息相關。如果理解不清,會導致點擊判斷錯誤、事件綁定邏輯混亂,甚至出現無法預期的行為。本文將從概念、重點摘要,再到程式碼範例,逐步說明 e.targete.currentTarget 的異同,並介紹實際開發中如何正確使用。


重點摘要

  1. 事件冒泡(Event Bubbling)

    • 當某個元素觸發事件時,事件會從最深層的子元素向外層父元素依序傳遞。
    • 這意味著外層元素的事件監聽器也可能被觸發。
  2. e.target

    • 代表「事件最初發生的元素」。
    • 使用者實際點擊或操作的 DOM 元素,不論事件是否冒泡到其他父層。
  3. e.currentTarget

    • 代表「當前正在處理事件的元素」。
    • 永遠指向事件監聽器綁定的元素。
  4. 差異核心

    • e.target = 使用者操作的來源元素。
    • e.currentTarget = 當前呼叫事件處理函式的元素。
  5. 常見用途

    • e.target:用於判斷使用者點擊了哪個子元素,常用於事件代理(event delegation)。
    • e.currentTarget:用於保證取得事件綁定的元素本身,避免因冒泡導致判斷錯誤。

範例一:基本點擊事件

以下是一個簡單的 HTML 結構:

<div id="outer" style="padding:20px; background:lightgray;">
Outer Div
<button id="inner">Click Me</button>
</div>

<script>
const outer = document.getElementById('outer');

outer.addEventListener('click', (e) => {
console.log('target:', e.target.id);
console.log('currentTarget:', e.currentTarget.id);
});
</script>

執行結果

  1. 點擊按鈕(id="inner"):

    • e.target.idinner
    • e.currentTarget.idouter
  2. 點擊外層灰色區塊(id="outer"):

    • e.target.idouter
    • e.currentTarget.idouter

說明

  • 使用者實際點擊按鈕,所以 e.targetinner
  • 但因為事件冒泡,outer 的監聽器被觸發,因此 e.currentTarget 永遠是 outer
  • 這正好展示了兩者的不同:一個描述事件來源,一個描述處理者。

範例二:事件代理(Event Delegation)

事件代理是一種常見技巧,將監聽器掛在父層元素,由它統一處理子元素的事件。這種情境下,e.target 就非常重要。

<ul id="list">
<li data-id="1">Item 1</li>
<li data-id="2">Item 2</li>
<li data-id="3">Item 3</li>
</ul>

<script>
const list = document.getElementById('list');

list.addEventListener('click', (e) => {
console.log('target tag:', e.target.tagName);
console.log('currentTarget tag:', e.currentTarget.tagName);

if (e.target.tagName === 'LI') {
console.log('You clicked item with data-id:', e.target.dataset.id);
}
});
</script>

執行結果

  • 點擊 Item 2 時:

    • e.target.tagNameLI
    • e.currentTarget.tagNameUL
    • 印出 "You clicked item with data-id: 2"

說明

  • list(UL)負責監聽整個清單,這就是 e.currentTarget
  • 實際點擊的 <li> 元素才是 e.target
  • 藉由 e.target,我們能知道使用者點的是哪個清單項目,而不用對每個 <li> 綁定監聽器。

範例三:模擬 Vue 的 .self 行為

在 Vue 中有一個 .self 修飾符,代表「只有點擊元素本身,而不是子元素時,事件才觸發」。這其實就是透過 e.target === e.currentTarget 判斷。

<div id="modal" style="width:300px; height:200px; background:rgba(0,0,0,0.5);">
<div style="width:100px; height:100px; background:white; margin:50px auto;"></div>
</div>

<script>
const modal = document.getElementById('modal');

modal.addEventListener('click', (e) => {
if (e.target === e.currentTarget) {
console.log('Clicked on modal background, close modal');
} else {
console.log('Clicked on inner content, do nothing');
}
});
</script>

說明

  • 點擊外層灰色背景時:e.target === e.currentTarget → 成立 → 關閉彈窗。
  • 點擊白色內層時:e.target 為內層 div,而 e.currentTarget 為外層 → 不相等 → 不關閉。

這個邏輯就是框架中 .self 修飾符的實作原理。


總結

  • e.targete.currentTarget 都是事件物件的重要屬性。
  • e.target 表示事件來源,是使用者實際操作的元素。
  • e.currentTarget 表示事件處理者,是目前綁定監聽器的元素。
  • 理解這兩者的差別,對於處理事件冒泡、事件代理、以及控制複雜 UI 行為都至關重要。

當我們需要知道「使用者點了哪個子元素」時,請使用 e.target。當我們需要確保「正在處理事件的元素本身」時,請使用 e.currentTarget

掌握這個觀念,能讓我們在撰寫事件監聽邏輯時更精準,避免誤判來源,也能設計出更乾淨與可維護的程式碼。

Git 指令學習筆記 | 學習筆記

· 閱讀時間約 6 分鐘
kdchang

Git 是現代軟體開發中最常使用的分散式版本控制系統,它幫助開發者管理源代碼的版本和協作。這篇教學將從基礎開始,介紹 Git 的常用命令並提供實際範例,幫助你熟悉如何使用 Git 來進行版本控制與協作。

1. 初始化 Git 倉庫 (git init)

在專案資料夾中使用 git init 可以初始化一個新的 Git 倉庫,這樣就可以開始使用 Git 來管理專案中的檔案版本。

git init

執行後會在專案資料夾內建立一個 .git 的隱藏資料夾,這表示這個資料夾已經被 Git 所管理。

2. 查看當前狀態 (git status)

在進行版本控制時,使用 git status 可以查看專案的當前狀態。這個命令會顯示哪些檔案已經被修改、哪些檔案尚未加入版本控制。

git status

這會列出所有變更過的檔案(包括已修改、尚未追蹤的檔案等)以及當前分支的狀態。

3. 新增檔案到暫存區 (git add)

修改檔案後,必須先使用 git add 把檔案新增到暫存區,才可以進行提交。使用 git add 可以指定單一檔案或多個檔案,或者一次將所有檔案加入暫存區。

git add filename.txt

或將所有變更加入暫存區:

git add .

4. 提交變更 (git commit)

將檔案從暫存區提交到本地版本庫使用 git commit。每次提交時,需要提供一個簡短的描述,解釋這次提交的內容。

git commit -m "新增使用者登錄功能"

5. 設定 Git 使用者資訊 (git config)

在進行提交之前,必須設定 Git 的使用者名稱和電子郵件。這些資料會與每次提交關聯。

git config --global user.name "Your Name"
git config --global user.email "your.email@example.com"

這會把你的名稱和郵箱寫入全域設定,未來每次提交都會使用這些設定。

6. 查看提交歷史 (git log)

使用 git log 可以查看專案的提交歷史。這會顯示每次提交的詳細信息,包括提交哈希值、作者、日期和提交訊息。

git log

如果希望更簡潔的輸出,可以使用 --oneline 參數:

git log --oneline

7. 恢復檔案 (git restore)

如果你修改了一些檔案並且還沒有提交,可以使用 git restore 恢復檔案到上次提交的狀態,撤銷變更。

git restore filename.txt

如果想撤銷暫存區的變更,可以使用:

git restore --staged filename.txt

8. 追蹤檔案變動紀錄 (git blame)

如果你想查看某個檔案每一行是誰在什麼時候修改的,可以使用 git blame

git blame filename.txt

這會顯示每一行的修改者與修改時間,有助於了解程式碼的歷史。

9. 建立分支 (git branch)

在 Git 中,分支讓你能夠並行地進行多個工作。你可以使用 git branch 查看目前所有的分支。

git branch

要創建新的分支:

git branch feature-login

10. 切換分支 (git switch)

要切換到不同的分支,可以使用 git switch。例如,切換到 feature-login 分支:

git switch feature-login

如果你要創建並切換到一個新的分支,可以加上 -c 選項:

git switch -c feature-register

11. 切換提交紀錄或分支 (git checkout)

雖然 git switch 是較新的命令,但仍然可以使用 git checkout 切換分支或還原檔案。要切換到某個分支:

git checkout feature-login

若要還原某個檔案到指定的提交:

git checkout <commit_hash> filename.txt

12. 合併分支 (git merge)

將不同分支的更改合併回主分支可以使用 git merge。首先切換到目標分支(例如 main),然後執行合併操作:

git switch main
git merge feature-login

如果有衝突,Git 會提示你解決衝突。

13. 重新整理提交歷史 (git rebase)

git rebase 用來重新整理提交歷史,將一個分支的更改重新應用到另一個分支上,使歷史更為線性。

git switch feature-login
git rebase main

這會將 feature-login 分支的變更,重新應用到 main 分支的最新提交上。

14. 重置提交 (git reset)

如果你想取消最近一次的提交,可以使用 git reset。使用 --soft 保留檔案變更,--hard 則會刪除變更:

git reset --soft HEAD^

這會將上一個提交撤回,但保留檔案變更;若使用 --hard,則會完全撤回提交及變更。

15. 查看操作紀錄 (git reflog)

git reflog 可以幫助你查看 HEAD 的操作紀錄,即便你進行了 resetcheckout 等操作。

git reflog

這個命令列出所有曾經指向 HEAD 的 commit 參考,並且可以用來恢復丟失的提交。

16. 暫存變更 (git stash)

如果你正在進行某些變更,但又需要切換到其他分支處理緊急問題,可以使用 git stash 暫時儲存未提交的變更:

git stash

這會暫時保存變更,並恢復工作目錄到最後一次提交的狀態。待你完成緊急工作後,可以使用 git stash pop 恢復變更。

17. 選擇性地應用提交 (git cherry-pick)

git cherry-pick 讓你可以選擇性地將某個提交應用到當前分支。例如,將 feature-login 分支的某個提交應用到 main 分支:

git cherry-pick <commit_hash>

這對於選擇性地將某些特定變更移動到其他分支非常有用。

18. 子模組 (git submodule)

當專案中有其他專案作為依賴時,可以使用 Git 子模組來管理。首先,將子模組加入專案:

git submodule add <repository_url> path/to/submodule

這會在專案中加入一個指向其他 Git 倉庫的子模組。在克隆專案時,記得加上 --recurse-submodules 來獲取子模組的內容。


總結

以上介紹了 Git 中一些常用的命令,這些命令幾乎涵蓋了日常開發中的大部分需求。熟練掌握 Git 讓你在開發過程中更加高效,無論是單人開發還是團隊協作,都能大大提高工作流的順暢性。如果你還有進一步的需求或疑問,隨時可以深入學習 Git 的進階技巧。

Git 入門教學筆記:從零開始學版本控制 | 學習筆記

· 閱讀時間約 4 分鐘
kdchang

為什麼要學 Git?

在軟體開發中,版本控制是一個不可或缺的工具。Git 是目前最流行的版本控制系統,它能幫助你:

  • 記錄每次修改的歷史紀錄
  • 回復到任一個過去的狀態
  • 多人協作開發不衝突
  • 管理分支進行實驗性開發

無論是獨立開發者、團隊協作,還是參與開源專案,Git 都是你的好夥伴。


一、安裝 Git

macOS

brew install git

Ubuntu / Debian

sudo apt update
sudo apt install git

Windows

請至官網下載安裝:https://git-scm.com


二、基本設定

第一次使用 Git 前,請先設定你的名稱與 Email(這會記錄在每次提交的資訊中):

git config --global user.name "你的名字"
git config --global user.email "you@example.com"

你也可以檢查目前的設定:

git config --list

三、建立本地倉庫(repository)

建立一個新資料夾並初始化 Git

mkdir my-project
cd my-project
git init

此時 .git/ 目錄會被建立,代表你已經在這個資料夾啟用了版本控制。


四、版本控制的基本流程

Git 的工作流程大致分為三個區域:

  1. 工作區(Working Directory):你實際修改的檔案
  2. 暫存區(Staging Area):準備提交的檔案
  3. 版本庫(Repository):正式提交的版本歷史

1. 查看狀態

git status

2. 將檔案加入暫存區

git add 檔名
# 或加入全部變動檔案
git add .

3. 提交版本(commit)

git commit -m "加入了首頁 HTML 結構"

五、版本紀錄與回溯

查看提交紀錄

git log

如果想要簡潔查看

git log --oneline

回到先前的狀態(例如最近一次提交)

git checkout HEAD^
# 回到上上一個版本
git checkout HEAD~2

但請注意這樣會進入「detached HEAD」狀態,只是暫時查看,不適合繼續開發。


六、分支操作(branch)

分支是 Git 強大的一環,可以讓你同時開發多個功能而互不干擾。

建立新分支

git branch feature-login

切換分支

git checkout feature-login

或合併成一行:

git checkout -b feature-login

合併分支

切回主分支(通常是 mainmaster):

git checkout main
git merge feature-login

七、遠端倉庫(Remote Repository)

若你使用 GitHub、GitLab 等服務,可以將本地專案推送到遠端:

新增遠端倉庫

git remote add origin https://github.com/yourname/yourproject.git

推送本地分支

git push -u origin main

拉取遠端最新變更

git pull origin main

八、.gitignore 檔案

.gitignore 用來指定不想被 Git 管理的檔案,例如:

node_modules/
.env
*.log
.DS_Store

建立一個 .gitignore 檔案,放進你專案根目錄,就能避免把不該版本控制的東西送到遠端倉庫。


九、常見狀況與解法

修改後不小心沒 add 就 commit 了?

可用下列方式補上:

git add 新增的檔案
git commit --amend

想撤銷修改?

還沒 add 的:

git checkout -- 檔案名

已經 add 了但還沒 commit:

git reset HEAD 檔案名

十、總結:從習慣開始,讓版本控制變簡單

熟悉 Git 的過程一開始可能會覺得繁瑣,但當你習慣了之後,它會成為你每天工作中不可或缺的工具。建議你在小專案中練習基本指令,隨手記錄每次的變更,透過良好的 commit message,讓未來的你感謝現在有好好整理的你。

如果你希望與他人協作開發,Git 更能大幅簡化流程、避免衝突。下一步,你可以學習 Pull Request(PR)、Git Flow、Rebase 等進階技巧,逐步精進你的版本控制能力。

參考文件

  1. Conventional Commits
  2. 約定式提交
  3. [Git] Conventional Commits 規範性提交
  4. git reflog - 還原大招
  5. 為你自己學 Git

JavaScript this 使用情境入門教學筆記 | 學習筆記

· 閱讀時間約 3 分鐘
kdchang

在 JavaScript 中,箭頭函式(arrow function)與傳統函式在 this 的行為上有一些不同。箭頭函式不會創建自己的 this,而是繼承外部作用域的 this,這就是為什麼你會遇到 this 的問題。

傳統函式中的 this

在傳統的函式中,this 會指向該函式被調用時的上下文。例如,當函式作為事件處理器時,this 會指向觸發事件的元素。

function regularFunction() {
console.log(this); // 'this' 是調用它的上下文
}

const obj = {
name: 'KD',
show: regularFunction
};

obj.show(); // 'this' 會指向 obj

箭頭函式中的 this

在箭頭函式中,this 並不會綁定到函式的上下文,而是繼承自外部作用域。這通常會讓箭頭函式的 this 變得不同於你預期的結果。

例子:this 的問題

假設有這樣的情境,當你在一個物件的方法中使用箭頭函式作為事件處理器,this 會指向外部作用域,而不是該物件本身。

const obj = {
name: 'KD',
show: function() {
// 使用傳統函式作為事件處理器
document.getElementById('btn').addEventListener('click', function() {
console.log(this); // 'this' 指向的是 'btn' 按鈕元素
});

// 使用箭頭函式作為事件處理器
document.getElementById('btn').addEventListener('click', () => {
console.log(this); // 'this' 指向外部作用域,即 'obj' 物件
});
}
};

obj.show();
  • 在傳統函式中,this 會指向觸發事件的 DOM 元素(這裡是 btn 按鈕)。
  • 在箭頭函式中,this 會指向外部作用域(這裡是 obj 物件),因為箭頭函式不會創建自己的 this

解決方案:確保 this 正確指向

如果你希望 this 指向物件本身,可以使用傳統函式或手動綁定 this

使用傳統函式

如果你希望在事件處理器中讓 this 指向物件本身,可以使用傳統函式,或者使用 bind 顯式綁定 this

const obj = {
name: 'KD',
show: function() {
document.getElementById('btn').addEventListener('click', function() {
console.log(this); // 'this' 指向 obj
}.bind(this)); // 顯式綁定 'this' 到 obj
}
};

obj.show();

另一個選項:箭頭函式和外部 this

如果你希望繼續使用箭頭函式,你可以將物件的 this 儲存到外部變數中,並在箭頭函式中引用它。

const obj = {
name: 'KD',
show: function() {
const self = this; // 保存物件的 'this'
document.getElementById('btn').addEventListener('click', () => {
console.log(self); // 使用外部的 'self',指向 obj
});
}
};

obj.show();

總結

  • 傳統函式 會根據調用上下文決定 this 的值。
  • 箭頭函式 會繼承外部作用域的 this,不會創建自己的 this,這樣在某些情況下會導致 this 不如預期。
  • 如果你需要在事件處理器中使用物件的 this,可以選擇使用傳統函式或顯式綁定 this

JavaScript 多事件處理綁定使用情境入門教學筆記 | 學習筆記

· 閱讀時間約 3 分鐘
kdchang

在 JavaScript 中,你可以通過事件處理的方式來為多個 input 元素綁定事件處理器。這樣做可以讓你在父容器上綁定一個事件處理器,並通過 event.target 確定觸發事件的具體 input 元素,而不需要為每個 input 元素單獨綁定事件。

事件委派的概念

事件委派是一種常見的事件處理技術,它將事件綁定到父元素或容器上,然後通過 event.target 來確定哪個子元素觸發了事件。這種方式在動態生成的元素中非常有用,因為無論多少個元素,它們都會使用相同的事件處理器。

例子:為多個 input 元素使用事件委派

假設有多個 input 元素,並且我們希望根據用戶在每個 input 中的輸入執行某些操作,可以像這樣使用 event.target

<div id="inputContainer">
<input type="text" id="input1" />
<input type="text" id="input2" />
<input type="text" id="input3" />
</div>

<script>
// 綁定事件處理器到父容器
document.getElementById('inputContainer').addEventListener('input', function(event) {
// 檢查事件目標是否為 input 元素
if (event.target.tagName.toLowerCase() === 'input') {
console.log('觸發的 input 元素 ID:', event.target.id);
console.log('輸入的值:', event.target.value);
}
});
</script>

解釋:

  1. 我們將 input 元素的 input 事件綁定到父容器 #inputContainer 上。
  2. 當任何一個 input 元素觸發 input 事件時,事件會冒泡到父容器,並且事件處理器會被執行。
  3. 在事件處理器中,我們使用 event.target 來確定是哪個 input 元素觸發了事件。event.target 會返回實際觸發事件的元素。
  4. 通過 event.target.idevent.target.value,我們可以獲取觸發事件的 input 元素的 ID 和輸入的值。

優點:

  • 減少事件綁定數量:不需要為每個 input 元素單獨綁定事件,減少了冗餘代碼。
  • 動態元素支持:即使後來添加了新的 input 元素,父容器上的事件處理器也會自動處理新元素。

input 元素是動態創建時:

如果你有動態創建的 input 元素,事件委派依然有效,因為事件處理器是綁定在父容器上的,而不需要直接綁定在每個 input 元素上。

// 假設需要動態創建 input 元素
const container = document.getElementById('inputContainer');

for (let i = 1; i <= 5; i++) {
const input = document.createElement('input');
input.type = 'text';
input.id = 'input' + i;
container.appendChild(input);
}

這樣,當你動態創建新的 input 元素時,父容器上的事件處理器會自動處理這些新的 input 元素。

JWT 使用好處入門教學筆記 | 學習筆記

· 閱讀時間約 3 分鐘
kdchang

前言

JWT(JSON Web Token) 的好處在於它是輕量、跨平台、無狀態的驗證與授權機制,特別適合用於前後端分離、微服務架構與 API 驗證。以下是它的主要優點:

一、JWT 的 7 大好處

1. 無狀態(Stateless)驗證

  • JWT 本身就包含用戶資訊與簽名,不需要在伺服器端儲存 session
  • 適合微服務或 Serverless 架構,因為伺服器不用記住誰是誰。

2. 跨平台、跨語言支援

  • JWT 是一種標準格式(RFC 7519),可用於各種語言與框架(Node.js、Python、Go、Java、PHP、.NET 等)。
  • 非常適合前後端分離的架構,或多平台應用(Web + App)。

3. 傳遞資訊方便

  • 可以在 payload 中攜帶使用者角色、權限、使用者 ID 等資料,前後端都可解碼取得。
  • 節省額外查詢資料庫的頻率(如登入後不再查資料庫即可知道使用者身份)。

4. 易於擴展與整合 OAuth

  • JWT 是 OAuth 2.0 中 Access Token 的常見格式。
  • 整合第三方登入(如 Google、Facebook、GitHub)時很常使用 JWT 作為驗證令牌。

5. 高安全性(搭配正確實作)

  • Token 是簽名過的,無法被竄改(除非持有密鑰)。
  • 可設定有效期限(exp),也支援附加自訂欄位。

6. 前端儲存與攜帶方便

  • JWT 是字串格式,可透過:

    • Authorization header:Bearer <token>
    • Cookie(推薦設為 HttpOnly + Secure)
    • localStorage(需防範 XSS)

7. 支援單一登入(SSO)

  • 多個子系統共用同一套 JWT 驗證邏輯,實現單一登入(Single Sign-On)。
  • 用於企業內部系統、微服務架構特別合適。

二、實際應用情境

應用場景優勢
API 驗證無狀態、易於整合
單頁應用(SPA)可存取使用者資訊而不額外請求
手機 App 登入移動端儲存與驗證方便
微服務架構每個服務只需驗證簽章,無需共用 session
OAuth 第三方登入可當作 Access Token 與 ID Token

三、與 Session 的比較

項目JWTSession
資料儲存位置前端(Token 傳遞)後端(記憶體/資料庫)
可擴充性高(無需共享記憶體)需維護集中式 Session 儲存
實作難度中(需處理 Token 過期、黑名單等)簡單(有框架支援)
安全性好(但需避免 XSS 與 Token 洩露)好(但可能遭 CSRF 攻擊)

四、常見誤解釐清

常見誤解釐清
JWT 是加密的錯,預設只是簽名(不可竄改),不是加密(不可閱讀)
JWT 安全無敵錯,若密鑰外洩,任何人都能簽 Token。需配合 HTTPS 傳輸與妥善保護密鑰
JWT 不需要過期時間錯,若未設 exp,Token 可永久有效,極度危險

五、總結

JWT 的優勢在於簡潔、無狀態、可擴展,特別適合 API 驗證與前後端分離架構。然而,要發揮 JWT 的最大效益並保障安全,開發者需了解其限制,搭配正確實作(如過期、黑名單、HTTPS、Refresh Token 管理等)。

React 前端整合 JWT(含 Refresh Token)入門教學筆記 | 學習筆記

· 閱讀時間約 4 分鐘
kdchang

前言

當你要將 JWT 驗證整合至 React 前端,並搭配 Refresh Token 或整合 OAuth(如 Google 登入)流程,你需要考慮前端的存儲方式、token 的更新機制,以及第三方登入的流程銜接。

以下將分成三個部分講解:


一、React 前端整合 JWT(含 Refresh Token)

流程總覽:

  1. 使用者輸入帳密登入,發送 /login 請求。

  2. 伺服器簽發兩種 token:

    • Access Token(短效,有效期如 15 分鐘)
    • Refresh Token(長效,有效期如 7 天)
  3. 前端儲存 Access Token(如在記憶體),Refresh Token 建議儲存在 HttpOnly Cookie

  4. 當 Access Token 過期時,自動用 Refresh Token 換取新 Access Token。

  5. 使用者主動登出時,Refresh Token 一併清除。


伺服器回傳範例(登入成功):

{
"accessToken": "xxxxx",
"expiresIn": 900
}

並透過 Set-Cookie 傳送 HttpOnly 的 Refresh Token:

Set-Cookie: refreshToken=xxxxx; HttpOnly; Path=/; Max-Age=604800;

React 實作要點

// login.js
import axios from 'axios';

export async function login(username, password) {
const res = await axios.post(
'/api/login',
{ username, password },
{
withCredentials: true, // 允許 cookie 傳遞
}
);

localStorage.setItem('accessToken', res.data.accessToken);
}

// api.js
import axios from 'axios';

const api = axios.create({
baseURL: '/api',
withCredentials: true,
});

api.interceptors.request.use((config) => {
const token = localStorage.getItem('accessToken');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});

api.interceptors.response.use(
(res) => res,
async (err) => {
if (err.response.status === 401) {
try {
// 嘗試使用 Refresh Token 換 Access Token
const res = await axios.post('/api/refresh', {}, { withCredentials: true });
localStorage.setItem('accessToken', res.data.accessToken);

// 重試原本請求
err.config.headers.Authorization = `Bearer ${res.data.accessToken}`;
return axios(err.config);
} catch (e) {
// refresh 失敗,跳轉登入頁
window.location.href = '/login';
return Promise.reject(e);
}
}
return Promise.reject(err);
}
);

export default api;

登出

export async function logout() {
await axios.post('/api/logout', {}, { withCredentials: true });
localStorage.removeItem('accessToken');
}

備註

  • Refresh Token 儲存在瀏覽器的 HttpOnly Cookie,無法被 JavaScript 存取,提升安全性。
  • Access Token 儲存在記憶體或 localStorage(但 localStorage 易受 XSS 攻擊)。
  • 若要完全防止 CSRF,Refresh Token cookie 需搭配 SameSite 設定與 CSRF token。

二、OAuth 2.0 登入(以 Google 為例)

流程總覽

  1. 前端點擊「使用 Google 登入」。
  2. 透過 Google OAuth 流程取得授權碼(或 id_token)。
  3. 前端將該 token 傳送到後端 /auth/google
  4. 後端驗證 Google id_token,並簽發 JWT 給前端。

React 前端整合(Google 登入)

使用 @react-oauth/google

npm install @react-oauth/google
import { GoogleOAuthProvider, GoogleLogin } from '@react-oauth/google';

function App() {
return (
<GoogleOAuthProvider clientId="你的 Google Client ID">
<GoogleLogin
onSuccess={(credentialResponse) => {
// 將 id_token 傳送給後端
fetch('/api/auth/google', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ token: credentialResponse.credential }),
});
}}
onError={() => {
console.log('登入失敗');
}}
/>
</GoogleOAuthProvider>
);
}

後端處理(Node.js + google-auth-library

import { OAuth2Client } from 'google-auth-library';
import jwt from 'jsonwebtoken';

const client = new OAuth2Client(GOOGLE_CLIENT_ID);

router.post('/auth/google', async (req, res) => {
const { token } = req.body;

const ticket = await client.verifyIdToken({
idToken: token,
audience: GOOGLE_CLIENT_ID,
});

const payload = ticket.getPayload();

const jwtToken = jwt.sign({ userId: payload.sub, name: payload.name }, process.env.JWT_SECRET, {
expiresIn: '15m',
});

const refreshToken = jwt.sign({ userId: payload.sub }, process.env.JWT_REFRESH_SECRET, {
expiresIn: '7d',
});

res
.cookie('refreshToken', refreshToken, {
httpOnly: true,
maxAge: 7 * 24 * 60 * 60 * 1000,
})
.json({ accessToken: jwtToken });
});

三、小結

功能JWT + RefreshOAuth 2.0
是否需要帳密登入否,透過第三方登入
Token 儲存Access Token: localStorage / memory
Refresh Token: HttpOnly Cookie
同上
適合對象自建會員系統使用 Google / Facebook / LINE 等快速登入
安全性良好,需搭配 HTTPS高,由 Google 等大廠管理
實作難度中等,需處理 Token 刷新邏輯中高,需處理外部驗證流程

延伸建議

  • 若你想使用前後端共用的 JWT 驗證邏輯,建議抽出 middleware 並集中處理。
  • 可搭配 jsonwebtokenaxios-auth-refresh 等工具。
  • 若前後端完全分離,建議使用跨網域 Cookie 搭配 SameSite=None; Secure