2025/08/25

倉頡五代升級攻略:Windows 10/11使用者的完美解決方案 (含倉頡三代升級)

 

作者:吳祐賓

 

 

你使用倉頡打字的時候,有沒有感受到奇怪的味道:

* 晚上的「晚」永遠都排在第二個字

* 「卜竹竹手一」打不出「産」?而「木中中女」打不出「樓」?

 

這代表你也是微軟倉頡四代的受災戶

 

在學習倉頡五代後,一直覺得微軟倉頡和新倉頡的倉頡四代碼表很不友善,一直到最近看到倉頡之友的【替換微軟倉頡碼表,盡享倉頡補完計劃的樂趣】,才知道原來微軟倉頡也可以更新碼表,終於能享受到輸入文字的樂趣了!

 

首先,本篇文章適用範圍:Windows 10 2004 版以上。

 

 

倉頡碼表下載點

 

下載:MSCJData_Cangjie 開頭字樣檔案

例如: MSCJData_20230909_Cangjie3_WithExtI.7z  (三代) / MSCJData_Cangjie5_20240519.zip (五代) 

 

 

給倉頡三代的使用者 (習慣打法為:「木中田女」=樓;「戈土心」=應) 

 

倉頡三代補完計劃替換碼表下載地址:https://github.com/Arthurmcarthur/Cangjie3-Plus/releases

 

 


 

 

給倉頡五代的使用者 (習慣打法為:「木中中女」=樓;「戈人土心」=應)

 

倉頡五代補完計劃替換碼表下載地址:https://github.com/Jackchows/Cangjie5/releases

 

 

安裝步驟

 

  1. 任務管理器裏結束兩個Microsoft IME進程。
  2. 進入 C:\Windows\System32\zh-hk 目錄,刪除
    • ChtCangjie.sdc
    • ChtCangjie.spd
    • ChtCangjieExt.lex
    三個文件(刪除前請備份),然後將此處提供的 ChtCangjieExt.lex,複製到該目錄。




  3. 打開「包含香港增補字符集字元(HKSCS)」開關。

 

 

完成上述步驟後請重新開機,接下來就可以好好體驗倉頡五代所帶來的暢快輸入感!

 

相信更多愛用倉頡的愛好者不滿足於預設的內容。不過因為我個人沒用到進階設定,想要了解更多細節的倉頡愛好者,歡迎再閱讀補完計畫說明檔。


總結

 

使用倉頡補完計畫優點

 

  • 字碼編排最貼近符合倉頡歷代改版標準。
  • 可以使用倉頡五代輸入方式。 
  • 重難(ZX)符號輸入完整呈現。
  • 還有微軟倉頡獨門快速鍵 100% 保留,例如:
    Ctrl+, ,
    Ctrl+' 、
    Ctrl+. 。
    Ctrl+; ;
    Ctrl+Shift+; :

    `+ +
    `- -
    `/ /
    `/ 向下鍵 ÷
    `* 向下鍵 ×
    `+ 向下鍵 ±

    Ctrl+[ 【 向下鍵 「...
    Ctrl+] 】 向下鍵 」...

    Ctrl+Alt+, 叫出特殊符號表 (輸入法整合器) 

 

使用倉頡補完計畫缺點


  • Z開頭的符號碼無法使用,只能使用微軟輸入法的符號快速輸入鍵輸入。 (20220904 MSCJData 版本已解決此問題)
  • 由於微軟倉頡的排序邏輯寫死在程序中,與碼表沒有關聯。補完計畫在調整此問題已將部首、筆畫、兼容區字符移除,RIME 則無此問題

 

整體來說優點大於缺點,能暢快享受輸入文字的樂趣,在這邊分享給大家。

2025/08/23

EXARC (Express ARC JS Starter Kit) 為 Delphi 開發者打造的現代化 Web 開發框架

EXARC (Express ARC JS Starter Kit)
-- 為 Delphi 開發者打造的現代化 Web 開發框架

🎯 給熟悉 DataSnap、MVC、WebBroker 的您

如果您曾經使用過 Delphi DataSnap REST 的代理自動生成功能,或是體驗過 Delphi MVC Web Framework 的路由機制,那麼 EXARC 將會讓您感到非常熟悉。它將 Delphi 開發中最優雅的概念帶到了現代 JavaScript 生態系統中。


🔄 從 Delphi 到 JavaScript:相似的開發哲學

DataSnap REST 的現代化演進

還記得 Delphi DataSnap 如何自動為您的伺服器方法生成用戶端代理嗎?EXARC 採用了相同的理念:

Delphi DataSnap EXARC 相似度
伺服器方法定義 Express 路由定義 ✅ 相同概念
自動生成用戶端代理 自動生成 apiProxy.js ✅ 相同機制
型別安全的呼叫 語義化的 async 呼叫 ✅ 相同體驗
內建認證支援 JWT 認證中介軟體 ✅ 相同功能
// Delphi DataSnap 伺服器方法
function TServerMethods.EchoMessage(const AMessage: string): string;
begin
  Result := 'Echo: ' + AMessage;
end;

// 自動生成的用戶端代理呼叫
ClientModule.ServerMethods.EchoMessage('Hello');
// EXARC Express 路由定義
app.get('/api/echomsg/:msg', (req, res) => {
    res.json({ echoed: req.params.msg });
});

// 自動生成的前端代理呼叫
await apiProxy.echomsg('Hello');

MVC Web Framework 的路由理念

熟悉 Delphi MVC 的路由屬性嗎?EXARC 採用了相似但更簡潔的方式:

// Delphi MVC Controller
[MVCPath('/api/users')]
[MVCHTTPMethod([httpGET])]
function GetUserList: TObjectList<TUser>;
// EXARC Express 路由
app.get('/api/users/list', (req, res) => {
    res.json(getUserList());
});
// 自動生成: apiProxy.usersList()

💡 為什麼 Delphi 開發者會愛上 EXARC?

1. 熟悉的開發模式

您已經習慣了 Delphi 的「定義一次,處處可用」理念:

  • DataSnap: 定義伺服器方法 → 自動生成用戶端代理
  • EXARC: 定義 Express 路由 → 自動生成前端代理
  • 結果: 相同的開發體驗,但擁抱現代 Web 技術

2. 強型別思維的延續

雖然是 JavaScript,但 EXARC 透過 JSDoc 和自動生成機制,提供了接近強型別的開發體驗:

/**
 * 建立使用者資料
 * @param {Object} userData - 使用者資訊
 * @param {string} userData.name - 使用者姓名
 * @param {string} userData.email - 電子郵件
 * @returns {Promise<Object>} 建立結果
 */
app.post('/api/users/create', (req, res) => {
    // 業務邏輯實作
});

// 前端呼叫時會有清楚的參數提示
await apiProxy.usersCreate({
    name: "John Doe",
    email: "john@example.com"
});

3. 內建的測試和除錯工具

就像 Delphi IDE 的整合除錯環境,EXARC 提供內建的 API 測試器:

  • 自動掃描所有 API 端點(類似 DataSnap 的伺服器方法瀏覽器)
  • 即時參數輸入和測試(類似 Delphi 的 Method Invoker)
  • 即時回應查看(類似 REST Debugger)

🚀 技術架構對比

從 WebBroker 到 Express.js

概念 Delphi WebBroker EXARC Express.js
HTTP 伺服器 TWebModule + HTTP.sys Node.js + Express
路由處理 WebActionItem app.get/post/put/delete
中介軟體 WebModule Events Express Middleware
靜態檔案 DocumentRoot express.static
會話管理 TWebSession JWT Token

從 DataSnap 到 EXARC

// Delphi DataSnap 架構
TServerContainer -> TDSServer -> TServerMethods
                              -> Auto-generated Client Proxy
// EXARC 架構
Express Router -> API Routes -> Auto-generated apiProxy.js
                            -> Dynamic API Invoker

🔧 實際開發對比

Delphi DataSnap 開發流程

// 1. 定義伺服器方法
function TServerMethods.GetCustomerOrders(CustomerID: Integer): TJSONArray;
begin
  // 實作業務邏輯
end;

// 2. 編譯專案,自動更新用戶端代理

// 3. 用戶端呼叫
ClientModule.ServerMethods.GetCustomerOrders(123);

EXARC 開發流程

// 1. 定義 Express 路由
app.get('/api/customers/:id/orders', (req, res) => {
    const customerId = req.params.id;
    // 實作業務邏輯
    res.json(getCustomerOrders(customerId));
});

// 2. 儲存檔案,自動更新前端代理

// 3. 前端呼叫
await apiProxy.customersOrders(123);

相似度:95%!


🌟 EXARC 相比 Delphi 的優勢

1. 現代化的生態系統

  • 豐富的套件庫: npm 生態系統 vs GetIt Package Manager
  • 跨平台部署: Linux/Windows/macOS vs Windows 主要
  • 容器化支援: Docker 原生支援 vs 需要額外配置

2. 前端技術選擇自由

  • 多框架支援: React, Vue, 原生 JS vs 主要是 Delphi 用戶端
  • 響應式設計: 內建 Mobile-first 設計
  • 現代 UI 框架: Tailwind, Bootstrap 等

3. 開發工具整合

  • VS Code 完整支援: IntelliSense, 除錯, Git 整合
  • 熱重載: 程式碼變更即時生效
  • DevTools: 強大的瀏覽器除錯工具

4. 部署和維運

  • 雲端原生: 天然支援 AWS, Azure, GCP
  • CI/CD 整合: GitHub Actions, GitLab CI 等
  • 監控和日誌: 豐富的 APM 工具

🎯 遷移建議:從 Delphi 到 EXARC

階段一:概念對應

  • DataSnap 伺服器方法 → Express 路由
  • ClientDataSet → JSON 資料處理
  • WebBroker Actions → Express 中介軟體
  • ISAPI/CGI → Node.js HTTP Server

階段二:逐步遷移

  1. API 層先行: 將 DataSnap REST 服務改寫為 Express API
  2. 保持資料庫: 繼續使用現有的 SQL Server/Oracle/FireDAC
  3. 前端現代化: 逐步將 VCL/FMX 用戶端改為 Web 介面
  4. 認證統一: 從 Session-based 遷移到 JWT

階段三:現代化升級

  • 導入 TypeScript (可選)
  • 整合現代 CSS 框架
  • 實作 PWA 功能
  • 加入自動化測試

📊 成本效益分析

Delphi 開發 vs EXARC 開發

項目 Delphi 生態 EXARC + Node.js
授權成本 Delphi Professional: $1,500+ 完全免費
伺服器授權 Windows Server 授權 Linux 免費
部署成本 IIS + Windows Docker + 雲端 (更便宜)
維護成本 需要 Delphi 專家 JavaScript 開發者更易找
學習曲線 對 Delphi 開發者: 低 中等 (但概念相似)

🛠️ 實作範例:從 DataSnap 到 EXARC

Delphi DataSnap 範例

unit ServerMethodsUnit;

interface

uses
  SysUtils, Classes, DSServer, DSAuth, DataSnap.DSProviderDataModuleAdapter,
  Datasnap.DSClientMetadata, Data.FireDACJSONReflect, Data.DB;

type
  TServerMethods = class(TDSServerModule)
  private
  public
    function GetUserProfile(UserID: Integer): TJSONObject;
    function UpdateUserProfile(UserID: Integer; ProfileData: TJSONObject): Boolean;
  end;

implementation

function TServerMethods.GetUserProfile(UserID: Integer): TJSONObject;
begin
  // 實作取得使用者資料邏輯
  Result := TJSONObject.Create;
  Result.AddPair('id', TJSONNumber.Create(UserID));
  Result.AddPair('name', 'John Doe');
end;

function TServerMethods.UpdateUserProfile(UserID: Integer; ProfileData: TJSONObject): Boolean;
begin
  // 實作更新使用者資料邏輯
  Result := True;
end;

EXARC 對應實作

// server.js
const express = require('express');
const app = express();

app.use(express.json());

// 對應 TServerMethods.GetUserProfile
app.get('/api/users/:id/profile', (req, res) => {
    const userId = parseInt(req.params.id);
    
    // 實作取得使用者資料邏輯
    const userProfile = {
        id: userId,
        name: 'John Doe',
        email: 'john@example.com'
    };
    
    res.json(userProfile);
});

// 對應 TServerMethods.UpdateUserProfile
app.put('/api/users/:id/profile', (req, res) => {
    const userId = parseInt(req.params.id);
    const profileData = req.body;
    
    // 實作更新使用者資料邏輯
    const success = updateUserProfile(userId, profileData);
    
    res.json({ success: success });
});

app.listen(3000);

自動生成的前端代理

// apiProxy.js (自動生成)
export const apiProxy = {
    /**
     * 取得使用者資料
     * @param {number} id - 使用者 ID
     * @returns {Promise<Object>} 使用者資料
     */
    usersProfile: async (id) => {
        return handleApiCall(api.get, `users/${id}/profile`);
    },

    /**
     * 更新使用者資料
     * @param {number} id - 使用者 ID  
     * @param {Object} data - 更新資料
     * @returns {Promise<Object>} 更新結果
     */
    usersProfileUpdate: async (id, data) => {
        return handleApiCall(api.put, `users/${id}/profile`, data);
    }
};

// 使用方式 (類似 DataSnap 用戶端代理)
const profile = await apiProxy.usersProfile(123);
const result = await apiProxy.usersProfileUpdate(123, { name: 'Jane Doe' });

🎉 結論:擁抱現代化,保持熟悉感

EXARC 為 Delphi 開發者提供了一條平滑的現代化遷移路徑:

保留 Delphi 的優雅設計理念 - 自動化、強型別思維、整合開發體驗
擁抱現代 Web 技術 - JavaScript 生態、雲端原生、跨平台部署
降低學習成本 - 相似的概念映射、熟悉的開發流程
提升競爭力 - 更低的成本、更大的人才庫、更好的擴展性

立即開始您的現代化之旅

# 30 秒體驗 EXARC
npx degit Eden5Wu/EXARC my-modern-api
cd my-modern-api && npm install && npm run dev

# 訪問 http://localhost:8893/apiProxyInvoker.html
# 體驗類似 DataSnap Method Invoker 的測試工具!

從 Delphi 到 EXARC,不是放棄過去,而是站在巨人的肩膀上擁抱未來。 🚀


📞 技術支援

  • GitHub: Eden5Wu/EXARC
  • 遷移指南: [專為 Delphi 開發者準備的詳細文檔]
  • 範例專案: [DataSnap 到 EXARC 的完整遷移範例]

讓我們一起將 Delphi 的優雅帶入現代 Web 開發! 💪

EXARC (Express ARC JS Starter Kit) 革命性的前後端開發框架

 EXARC (Express ARC JS Starter Kit) 革命性的前後端開發框架


🚀 告別重複工作,專注核心價值

EXARC 是一個基於 Express.js 的輕量級後端開發模板,專為追求高效開發體驗的團隊而設計。它徹底改變了傳統的前後端開發模式,讓您只需專注於業務邏輯的實現,其餘一切交給自動化處理。


💡 核心理念:後端驅動,前端自動同步

以後端為唯一真實來源 (Backend as the Single Source of Truth)

傳統開發中,前後端 API 介面需要分別維護,容易造成不同步問題。EXARC 創新地將這個流程完全自動化:

  • 後端定義路由 → 前端 API 代理自動生成
  • API 文件自動更新 → 測試工具即時同步
  • 零手動配置 → 開箱即用的完整解決方案

🎯 解決的核心痛點

1. 前後端介面不同步

  • 傳統方式:手動維護前端 fetch/axios 請求,容易與後端脫節
  • EXARC 方式:後端路由變更,前端代理自動更新

2. 重複的樣板程式碼

  • 傳統方式:每個 API 都需要寫相似的請求處理邏輯
  • EXARC 方式:一次定義,處處可用的語義化 API 呼叫

3. API 測試與文件維護

  • 傳統方式:需要額外工具如 Postman,文件容易過時
  • EXARC 方式:內建動態 API 調用器,測試與文件自動同步

4. 認證機制複雜性

  • 傳統方式:前後端認證邏輯分散,難以維護
  • EXARC 方式:統一的 JWT 認證流程,可一鍵切換啟用/停用

⚡ 核心特性

🔄 自動化 API 代理生成

// 後端:只需定義 Express 路由
app.get('/api/users/profile', (req, res) => {
    res.json({ user: 'profile data' });
});

// 前端:自動生成語義化呼叫
const profile = await apiProxy.usersProfile();

🧪 動態 API 測試器

  • 自動讀取後端路由,生成互動式測試介面
  • 支援 JSDoc 註解解析,提供完整的參數說明
  • 智能判斷輸入類型(JSON/純文字),零配置使用

🔐 彈性認證系統

  • 基於 JWT 的無狀態認證
  • 環境變數一鍵切換認證開關
  • 前後端獨立控制登入 UI 顯示

🌐 多框架支援

內建完整的範例,支援主流前端技術:

  • 原生 JavaScript - 輕量級實作
  • React - 現代組件化開發
  • Vue.js - 漸進式框架整合
  • jQuery Mobile - 移動端快速原型

📈 開發效率提升

傳統開發流程 vs EXARC 流程

階段 傳統方式 EXARC 方式 時間節省
API 定義 後端路由 + 前端請求函式 僅需後端路由 50%
介面同步 手動更新前端代碼 自動生成同步 90%
API 測試 配置 Postman 等工具 內建動態測試器 70%
文件維護 手動更新 API 文件 JSDoc 自動解析 80%

實際開發體驗

// 🎯 EXARC:四步驟完成 API 開發

// 1. 後端定義路由(唯一需要手寫的部分)
app.post('/api/orders/create', (req, res) => {
    const order = createOrder(req.body);
    res.json({ success: true, order });
});

// 2. 自動生成前端代理(無需任何操作)
// ✨ 伺服器重啟時自動更新 apiProxy.js

// 3. 前端直接使用(語義化呼叫)
const result = await apiProxy.ordersCreate(orderData);

// 4. 動態測試器自動更新(無需任何操作)
// ✨ 訪問 /apiProxyInvoker.html 即可測試

🏗️ 架構優勢

智能文件解析

  • 自動掃描 Express 路由堆疊
  • 將 REST 路徑轉換為駝峰式函式名稱
  • JSDoc 註解自動提取並生成文件

零配置哲學

  • 使用 degit 一鍵複製專案模板
  • npm run dev 即可啟動完整開發環境
  • 內建 nodemon 配置,避免無限重啟問題

生產就緒

  • 環境變數管理
  • 嚴格的 JWT 認證中介軟體
  • 完整的錯誤處理機制

🎨 適用場景

完美適合

  • 快速原型開發 - 將想法迅速轉化為可用產品
  • 中小型專案 - 需要高效開發流程的商業應用
  • API 優先設計 - 重視前後端分離的現代化項目
  • 團隊協作 - 需要統一開發規範的多人項目

技術團隊

  • 全端工程師 - 一人完成前後端開發
  • 前端開發者 - 快速建立後端 API 支援
  • 後端開發者 - 無需關心前端 API 調用細節
  • 專案經理 - 需要快速交付 MVP 的敏捷團隊

🚀 快速開始

30 秒啟動專案

# 1. 複製模板
npx degit Eden5Wu/EXARC my-awesome-project

# 2. 安裝依賴
cd my-awesome-project && npm install

# 3. 啟動開發
npm run dev

# 4. 開始編碼!
# 後端:編輯 server.js 新增 API
# 前端:直接使用 apiProxy.methodName()
# 測試:訪問 http://localhost:8893/apiProxyInvoker.html

📊 為什麼選擇 EXARC?

對比其他解決方案

特性 EXARC 傳統 Express Next.js API tRPC
學習曲線 ⭐⭐ ⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐
自動化程度 ⭐⭐⭐⭐⭐ ⭐⭐ ⭐⭐⭐⭐
開發速度 ⭐⭐⭐⭐⭐ ⭐⭐ ⭐⭐⭐ ⭐⭐⭐
靈活性 ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐
內建測試 ⭐⭐⭐⭐⭐ ⭐⭐

🎯 結語

EXARC 不只是一個開發框架,更是一種開發哲學的體現。

它相信開發者的時間應該花在創造價值上,而不是重複性的基礎設施建設。通過極致的自動化和智能化,EXARC 讓您能夠:

  • 🎯 專注業務邏輯 - 將創意直接轉化為代碼
  • 提升開發效率 - 用更少的時間交付更多價值
  • 🛡️ 降低維護成本 - 自動同步機制避免技術債務
  • 🚀 加速產品迭代 - 從想法到部署的最短路徑

選擇 EXARC,選擇更智慧的開發方式。


📞 立即開始

  • GitHub: Eden5Wu/EXARC
  • 快速開始: npx degit Eden5Wu/EXARC your-project-name
  • 授權: MIT License
  • 支援: Issues & Discussions on GitHub

讓 EXARC 成為您下一個專案的加速器! 🚀

2025/06/30

Deno 安裝篇

作者:吳祐賓 

 

 


 

Deno 開發的後端專案不會讓我再看到 node_modules 目錄,這是我超想使用 Deno 的理由

 

安裝方便更是讓我在業餘時間毫無壓力地學習它。 

 

 

安裝


Deno 能夠在 macOS、Linux 和 Windows 上運行。Deno 是一個單獨的可執行檔案,它沒有額外的依賴。 

 

下載

官方推薦使用指令碼操作

 

使用 Shell (macOS 和 Linux):


curl -fsSL https://deno.land/x/install/install.sh | sh

使用 PowerShell (Windows):


iwr https://deno.land/x/install/install.ps1 -useb | iex 


 

手動安裝

Deno 也可以手動安裝,只需從 github.com/denoland/deno/releases 下載一個 zip 檔案。它僅包含一個單獨的可執行檔案。在 macOS 和 Linux 上,您需要為它設定執行權限。 

 


 

手動安裝後設定環境變數

如果你不想每次開機都自動常駐,也可以在需要使用 Deno 時,使用指令將 Deno 執行檔所在路徑加入環境變數:

 

# Dos command 

SET PATH=%PATH%;D:\deno

# PowerShell

$env:path+=";D:\deno"

 

 


2025/06/13

【所謂的全端工程師,就是在面對突如其來的需求下,仍能給出合宜的解方】 ——打破程式鄙視鏈的迷思

作者:吳祐賓 

 



 

 


▋選擇 Classic ASP 來解決問題,不是瘋狂的叛逆,而是妥協的藝術



當面對一個微小需求:「使用者要在系統看到圖片」

而你那 2000 年開發的 Win32 系統既改不動,也不能改

一般人會有幾個選項:



1.說做不到,放棄

2.另外寫一個系統

 

選 1 的人很正常,領多少錢做多少事,老闆的薪水沒付到這個專業項目

選 2 的人超級認真,是一名做實事的員工,只是對應到「微小需求」的開發時間,聰明的老闆怎麼想都不認為是好的選項

身為一名專業的工程師,當然能不做就不做...我是說做不到的工作就給外包做



我選 3.外部連結或下載 來解決此問題,原因是:

1.繞過歷史包袱:避免於舊技術搏鬥、修改系統所引發的系統性風險 (好像繞口令)

2.成本效益最佳化:「使用者要在系統看到圖片」這句話的重點不是「在系統」,而是「看到圖片」,使用 OS 層的瀏覽器來開圖是成本最低、開發時間最短的方案




▋「成本最低、開發時間最短」的方案,你怎麼選?


 

利用瀏覽器開,可使用本機檔案開啟或另寫網頁圖片顯示功能

由於「本機檔案開啟」需要修改程式,不被許可,於是只能採用網頁圖片顯示

看到這裡,聰明的你應該會想到:



1.Python

2.Node.js

3.Deno

4.PHP

 

這些選項除了 4 之外,其它都是現代網頁開發的主流 (是,JSP 已成古代語了)

Python 絕對是開發時間最短的選項,豐富的三方資源,簡單直白的程式寫碼風格,都是上上之選

然而,除了開發環境複雜,開發除錯服務緩慢,網站服務還得設定反向代理(Reverse proxy),嗯,還是輕輕放下

Node.js / Deno 這兩個都是我目前的主要工具,豐富的 JS、三方資源,但為了實現小功能而承擔 node_modules 無底黑洞和使用反向代理等步驟,是我不考慮的原因

PHP,持續更新、活躍於現今的網頁開發市場,採 FastCGI,使 PHP 在穩定、執行效率更上一層樓,還是 NAS 的基本配備

但我的環境是 IIS + MSSQL 啊!PHP 的跨 NAS 機制對我來說吸引力不高,而 PHP 在 NAS 下要連 MSSQL 也是困難重重,點到 PHP 的技能樹對我的加分並不多




▋Classic ASP 位於程式鄙視鏈最底端,是它無用還是後門太多?


 
Classic ASP,目前版本 3.0(2000年2月17日,​距今至少 25 年前)

它還活著,Windows Server 2025 版,還是有內建 Classic asp 3.0

看到微軟廢棄了這麼多產品,IE6 都走入歷史了,Classic asp 居然還活著,證明 Classic asp 才是真正的歷史包袱

早些年,我也是臨危受命來解救公司內 asp 網站被殖入木馬的問題,所以對攻擊手法也是略懂略懂,不外乎就是被當 SQL 和 HTML 的測試場:

1. SQL 注入 (SQL injection)

2. XSS 注入 (Cross-Site Scripting,又稱跨站腳本攻擊)


綜合這些經驗,另外還有一個冷知識,asp 支援 JS ECMAScript 標準!就是 ECMA 3 (2000年標準) 舊了點

但它還是 JavaScript,在 JS 經驗上也能延續,哪怕 asp 是程式鄙視鍵的最底層,都不影響我使用 Classic asp 的決定




▋所謂的全端工程師,就是能在面對突如其來的需求下,仍能給出合宜的解方


 

透過這些分析可以知道,一名全端工程師,掌握一門可以前後端通透的技術外,巧妙的「技術選型」也是全端工程師必點的技能

使用 Classic asp,只是在我的場合下,能採用的最佳解,它可能不適合你,但上述的思路與大局觀,在任何場合都能適用

成為全端工程師,就是要持續的青春期叛逆


2025/05/09

還在 React 從入門到放棄?告訴你選擇比努力還重要的事

 作者:吳祐賓

 

 


 

 

用 Vite,別再用 CRA

 

2025年02月,React 官方公告,建議使用 Vite,所以別再用 CRA (create-react-app)

看了許多 React 的入門資料,還有很多古典資料,大多還是從 create-react-app 專案起始,用久了覺得很不舒服

 

NPM 編譯經常無預期出錯 


NPM 編譯經常無預期出錯,學習 React 免不了會經過一連串試錯的過程

但往往會不經意的出現許多編譯錯誤,例如在安裝某 UI 套件時,就遇到依賴套件版本衝突的問題,錯誤訊息洋洋灑灑一大篇,

指向的卻是 Webpack 內部模組的錯誤,對於當時的我來說,根本無從下手

在無法排除錯誤的情形下,最常做的就是重建 node_modules 內容,次數一多

是在學習 React 還是背誦 NPM i 指令,我已經搞不清楚了

 

 

在設計時期,前端和後端 API 網域位置不一致時

 

只要不是靜態網頁,在前端的開發過程一定會和後端 API 傳遞訊息,尤其是開發網頁應用程式更是必須

在設計時期,只要前端和後端的 Web 服務網域不同,就一定會遇上【跨來源資源共用(Cross-Origin Resource Sharing (CORS))】這個大魔王

後端就必須進行 CORS 設定,但實際上線又會是在同一網域,只有開發時期才會遇到的 CORS 顯得是非常詭異的歷程。

 

 

你以為要從 JavaScript ES6 開始學習,事實上

 

包含 React 官方網站,很多學習 React 的資料顯示,JavaScript ES6 是必須先學會的基礎,我認為這是造成 React 陡峭的學習曲線的罪魁禍首

許多社團新手遇到的問題大多是編譯 React 時產生的異常狀態,NPM 會回傳非常完整的【長篇報告】,也因為內容非常的多

若直接複製貼上,只會造成有心要幫忙的人在理解前還必須先把整篇報告看過一輪,除錯到心累。 



React 三大核心


React 是水很深的函式庫之一,技術中心思想在【狀態的傳遞】,基於中心所延伸出來的有三大核心:

 

  • React : 屬性、狀態、事件、元件和元件狀態互動等。
  • Router : 頁面到頁面間狀態的互動
  • Redux : 全域狀態管理

 

React 的問題大多就是狀態問題,Router 和 Redux 都是 React 官方沒做但是推薦的解決方案,都是學習 React 過程中必須學習的技能。

我歸納學習 React 學習資源後,發現從 create-react-app 幾乎就是給新人從入門到放棄的好專案,而我認為學習 React 路線有二:

  • 拿掉 NPM 編譯,純粹的 React
  • 擁抱編譯,從 Vite 開始


使用 esm.sh。拿掉 NPM 編譯,純粹的 React

 

 使用 esm.sh,就可以先專注在 React 的學習,esm.sh 也支援 JSX,範例如下:

 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<!doctype html>
<html>
<head>
<script type="importmap">
{
  "imports": {
    "react": "https://esm.sh/react@19.1.0",
    "react-dom/client": "https://esm.sh/react-dom@19.1.0/client"   
  }
}
</script>
<script type="module" src="https://esm.sh/tsx"></script>
</head>
<body>
  <div id="root"></div>
<script type="text/babel">
import React from "react";
import { createRoot } from 'react-dom/client';

const rootElement = document.getElementById("root");

const App = <h1>Hello, Eden</h1>;

const root = createRoot(rootElement);
root.render(App);

</script>
</body>
</html>

 

 <script type="importmap"> ... </script>

後面程式碼要使用 React 的時候,瀏覽器就知道要去 esm.sh 下載

 

 <script type="module" src="https://esm.sh/tsx"></script>

 esm.sh 讓瀏覽器看懂 JSX 語法

 

 

擁抱編譯,從 Vite 開始

 



在 npm 下,使用 vite cli 指令建立 vite react 專案


npm create vite@latest


接著按精靈指引,依序填入專案名稱、目錄名稱等內容


完成後,進入指定目錄就可以使用 npm 啟動 vite 建立的 React 網站,並開始學習 React 入門

 

  cd Eden-vite-project
  npm install
  npm run dev





2025/03/28

回歸網頁設計的原點 - HTMX

作者:吳祐賓













HTMX 和 VUE 一樣,是由個人開發的一款開源網頁函式庫 (Library,但 VUE 屬於 Framework,特此說明),前身名為 intercooler.js,由 Carson Gross 於 2013 年打造出的產品,在 2020 年改為 HTMX 後開始有知名度


▋HTMX 框架的核心目標



HTMX 核心目標,是為了讓 AJAX 變得簡單,丟掉 <a>、<form>


使用 html 標籤就能完成 AJAX 瑣碎的 JS 程式碼工作


2020 年在 HTMX 成立之初,就有多數開發者認為比 React、VUE、Angular 還要成熟的框架


由於 HTMX 和 React 同為 Library,所以,使用 React + HTMX 關鍵字查詢時,你會優先得到 HTMX is React killer 的比較資訊


更多的是教你如何從 React 移植到 HTMX 上


▋HTMX 實際設計



實際體驗相當簡單,不需要 Web Server,在單機就能體驗


▍首先,建立一個 html 檔案



▍在 html 引入 HTMX



雖然不用寫 js,但還是需要引入 HTMX 套件,另外,範例中會呼叫外部 API,所以要設定 "selRequestsOnly" 為 "false"


<script src="https://unpkg.com/htmx.org@2.0.4" integrity="sha384-HGfztofotfshcF7+8n44JQL2oJmowVChPTg48S+jvZoztPfvwD79OC/LTtG6dMp+" crossorigin="anonymous"></script>

<meta name="htmx-config" content='{"selfRequestsOnly": false}' />


▍建立 div,設定 HTMX 呼叫 API



沒有 API 怎麼辦?


你可以參考我的另一篇 "My JSON Server -- 偽線上 REST 服務",把 API 連結套入進來


在 body 標籤裡寫下


<div hx-get="https://my-json-server.typicode.com/Eden5Wu/react-store-api/products">Download Products</div>



你應該就可以看到如圖的成果展示

 




▋總結:HTMX 將 ajax 完美的整合到 html 裡



從上面的範例可以看到 HTMX 把 ajax 變不見,只有單純的 html,使用 HTMX 才能算是真正意義上的把 "寫網頁" 這件事變簡單


由於只用上 html,所以很適合以模板引擎為基礎的後端服務框架 (SSR) 來使用,能夠提高使用者提驗及網站 SEO,這給後端工程師加速寫出網頁應用程式的方法,像 PHP、Python 等都很合適


在 HTMX 官網中的 "Server-Side Examples" 一節,提供了一些使用模板引擎的後端服務框架,讓後端工程師可以參考,這裡列出幾個常見的後端服務框架:


▸JavaScript Node.js

▸C# ASP.NET Core

▸C# Blazor

▸PHP Laravel

▸Python Django

▸Python FastAPI

▸Delphi WebBroker

▸FreePascal with Pas2JS

2025/03/14

程式碼轉換不再難:AI 助你精通 FastReport FastScript

 作者:吳祐賓

 

 


 

 

 

FastScript 是 FastReport VCL / FMX 超棒的產品之一,FastScript 功能非常豐富,七成需求都可以依賴它來完成,不一定要使用肥大的開發工具。

 

FastScript 限制

 

FastScript 也有其限制,例如它不支援以下兩種東西:

  • Class 建立。無法使用 record, class 等 object 來建立自己的物件
  • Set 集合。無法使用 [fsBold, ...]來設定集合

 

使用 AI,在 FastScript 上寫出一手好程式


還好現在有 AI 工具,把提示語給 AI 後,就可以快速寫出正確又有效率的工具出來,真是太棒了!

 

我使用提示語如下,提供給各位讀者參考:


使用 FastReport FastScript 的 JScript 語法建立一個腳本:[
  將 "Hello, World!" 顯示在 Memo1 元件中。(可以代換為你的需求)
]
Refer to the following FastScript JScript syntax rules:[
===
Classes
You cannot define a class inside the script, but you can use the external classes defined in add-in modules or your application.
===
Functions
There is a rich set of standard functions which can be used in a
script. To get an access to these functions, pass the fsGlobalUnit
reference to the TfsScript.Parent property.
===
About FastReport's FastScript:JScript syntax:
Program -> Statements
Statements -> Statement...
Block -> '{' Statements '}'
ImportStmt -> IMPORT (String/,)...
VarStmt -> VAR (VarDecl/',')...
VarDecl -> Ident [Array] [InitValue]Array -> '[' (ArrayDim/',')... ']'
ArrayDim -> Expression
InitValue -> '=' Expression
Expression -> SimpleExpression [RelOp SimpleExpression]...
SimpleExpression -> ['-'] Term [AddOp Term]...
Term -> Factor [MulOp Factor]...
Factor -> Designator
-> UnsignedNumber
-> String
-> '(' Expression ')'
-> '!' Factor
-> '[' SetConstructor ']'
-> NewOperator
-> '<' FRString '>'
SetConstructor -> SetNode/','...
SetNode -> Expression ['..' Expression]
NewOperator -> NEW Designator
RelOp -> '>'
-> '<'
-> '<='
-> '>='
-> '!='
-> '=='
-> IN
-> IS
AddOp -> '+'
-> '-'
-> '||'
-> '^'
MulOp -> '*'
-> '/'
-> '%'
-> '&&'
-> '<<'
-> '>>'
Designator -> ['&'] Ident ['.' Ident | '[' ExprList ']' | '(' [ExprList] ')']...
ExprList -> Expression/','...
Statement -> (AssignStmt | CallStmt | BreakStmt | ContinueStmt |
DeleteStmt | DoWhileStmt | ForStmt | FunctionStmt |
IfStmt | ImportStmt | ReturnStmt | SwitchStmt |
VarStmt | WhileStmt | WithStmt | Block) [';']
BreakStmt -> BREAK
ContinueStmt -> CONTINUE
DeleteStmt -> DELETE Designator
AssignStmt -> Designator ['+'|'-'|'*'|'/']'=' Expression
CallStmt -> Designator ['+''+'|'-''-']
ReturnStmt -> RETURN [Expression]
IfStmt -> IF '(' Expression ')' Statement [ELSE Statement]
SwitchStmt -> SWITCH '(' Expression ')' '{' (CaseSelector)... [DEFAULT ':' Statement] '}'
CaseSelector -> CASE SetConstructor ':' Statement
DoWhileStmt -> DO Statement [';'] WHILE '(' Expression ')' ';'
WhileStmt -> WHILE '(' Expression ')' Statement
ForStmt -> FOR '(' ForStmtItem ';' Expression ';' ForStmtItem ')' Statement
ForStmtItem -> AssignStmt-> CallStmt-> VarStmt-> Empty
TryStmt -> TRY CompoundStmt (FINALLY | EXCEPT) CompoundStmt
FunctionStmt -> FunctionHeading Block
FunctionHeading -> FUNCTION Ident FormalParameters
FormalParameters -> '(' [FormalParam/','...] ')'
FormalParam -> ['&'] Ident
WithStmt -> WITH '(' Designator ')' Statement
]
Specific usage of FastScript JScript: [
#language JScript // this is optional
import "unit1.js", "unit2.js"
Report.Memo1.Text = "HelloWorld"; // Sets the text of Memo1 through the Report object.
Memo1.Text = "HelloWorld";// Equivalent if Memo1 is in the main report scope (not within a sub-component like a Group or Band).
var mbSet = mbYes + mbNo;  // FastScript does not support sets directly; use addition for combining flags.
if (MessageDlg("Welcome to my JScript application. Exit now?", mtConfirmation, mbSet, 0) == mrYes)
{ ShowMessage("OK");}
// import section - must be before any other sections
var i, j = 0; // var section
function p1() // procedures and function
{//
}
// main procedure that will be executed.
p1();
// Dynamic array create. (var myarray = [] not support. Need use the FR TfrxArray Component)
var myarray = new TfrxArray();
myarray[0] = 1;
myarray[1] = 2;
for (i = 0; i < 1; i++) j = i + 1; myarray[i] = myarray[i] + j;
ShowMessage(IntToStr(myarray.Count)); // TfrxArray is not JScript array type.
// You can use the TStrings/TStringList. Same the Delphi TStrings/TStringList.
var myList = new TStringList();
myList.Add("message=HelloWorld");
ShowMessage(myList.Values("message"));
]
The following FastScript built-in functions are available:
function IntToStr(i: Integer): Stringfunction FloatToStr(e: Extended): Stringfunction DateToStr(e: Extended): Stringfunction TimeToStr(e: Extended): Stringfunction DateTimeToStr(e: Extended): Stringfunction VarToStr(v: Variant): Stringfunction StrToInt(s: String): Integerfunction StrToFloat(s: String): Extendedfunction StrToDate(s: String): Extendedfunction StrToTime(s: String): Extendedfunction StrToDateTime(s: String): Extendedfunction Format(Fmt: String; Args: array): Stringfunction FormatFloat(Fmt: String; Value: Extended): Stringfunction FormatDateTime(Fmt: String; DateTime: TDateTime): Stringfunction FormatMaskText(EditMask: string; Value: string): stringfunction EncodeDate(Year, Month, Day: Word): TDateTimeprocedure DecodeDate(Date: TDateTime; var Year, Month, Day: Word)function EncodeTime(Hour, Min, Sec, MSec: Word): TDateTimeprocedure DecodeTime(Time: TDateTime; var Hour, Min, Sec, MSec: Word)function Date: TDateTimefunction Time: TDateTimefunction Now: TDateTimefunction DayOfWeek(aDate: DateTime): Integerfunction IsLeapYear(Year: Word): Booleanfunction DaysInMonth(nYear, nMonth: Integer): Integerfunction Length(s: String): Integerfunction Copy(s: String; from, count: Integer): Stringfunction Pos(substr, s: String): Integerprocedure Delete(var s: String; from, count: Integer): Stringprocedure Insert(s: String; var s2: String; pos: Integer): Stringfunction Uppercase(s: String): Stringfunction Lowercase(s: String): Stringfunction Trim(s: String): Stringfunction NameCase(s: String): Stringfunction CompareText(s, s1: String): Integerfunction Chr(i: Integer): Charfunction Ord(ch: Char): Integerprocedure SetLength(var S: String; L: Integer)function Round(e: Extended): Integerfunction Trunc(e: Extended): Integerfunction Int(e: Extended): Integerfunction Frac(X: Extended): Extendedfunction Sqrt(e: Extended): Extendedfunction Abs(e: Extended): Extendedfunction Sin(e: Extended): Extendedfunction Cos(e: Extended): Extendedfunction ArcTan(X: Extended): Extendedfunction Tan(X: Extended): Extendedfunction Exp(X: Extended): Extendedfunction Ln(X: Extended): Extendedfunction Pi: Extendedprocedure Inc(var i: Integer; incr: Integer = 1)procedure Dec(var i: Integer; decr: Integer = 1)procedure RaiseException(Param: String)procedure ShowMessage(Msg: Variant)procedure Randomizefunction Random: Extendedfunction ValidInt(cInt: String): Booleanfunction ValidFloat(cFlt: String): Booleanfunction ValidDate(cDate: String): Booleanfunction CreateOleObject(ClassName: String): Variantfunction VarArrayCreate(Bounds: Array; Typ: Integer): Variant


前面的提示詞也可以使用:

 

將以下 C# 程式碼片段轉換為使用 JScript 語法的 FastReport FastScript。 該腳本應在 FastReport 環境中實現等效的功能。 請密切注意所提供的 FastScript JScript 語法規則和可用的內建函數。


總結

有了AI後,程式轉換上變得十分容易,從以前要求 Google 關鍵字力,現在要漸漸改為 AI 提示詞力。

 

如果有什麼想回饋的內容,歡迎在底下留言讓我知道。

2025/03/07

C++ Builder記憶體管理:使用unique_ptr實現物件自動釋放

作者:吳祐賓

 

 

據說,未來是個沒有 Delete 的世界(大誤)



 

C++ Builder開發者:硬核技術與直覺UI的雙重追求

 

在說明物件釋放方法之前,還得先聊聊 C++ Builder 開發者的習慣

 

就目前所接觸到會使用 C++ Builder 的開發者所整理的經驗,他們使用 C++ Builder 的理由大致上是以下兩點:


  1. 本身具有極高的 C 語言造詣,通常具有硬體開發經驗
  2. 和 Visual Studio C++ 相比,C++ Builder 的 UI 更是直覺的建立


 

由 1 可知,會使用 C 語言的開發者,通常有很強烈的語言潔癖,以及有自己一套對記憶體控制的要求

 

由 2 可知,C++ Builder 對從 C 過來的開發者來說,是很棒的 UI 建模工具



C++ Builder (CB) 中的物件釋放:常見問題與解決方案探討

 
只是,由於 CB 的 WinForm 是建構在 VCL framework 之上,所以學習 C++ 的物件自然是必須要的。在擴充C++的基本知識後,底下便是常見的寫法:

2025/03/06

node.js 的字串拼接函式比較

 作者:吳祐賓

 

 

 


 

 

最近在寫 Express.js API 時,被 AI 提示說直接字串拼接較不安全,建議使用指令處理。

 

但採用後原來的程式反而無法執行。檢查後才發現原來組合出來的路徑和我想的不一樣。

 

於是就做了一點測試比較:

 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// node.js 的字串拼接函式比較:

// 1. 字串直接拼接
console.log('/A/B/C' + '/fetch_emp');
// 輸出 (所有系統, 但 Windows 上可能不正確): /A/B/C/fetch_emp
// 解釋:  直接使用 + 運算子。在 *nix (Linux/macOS) 上沒問題,但在 Windows 上可能因路徑分隔符 (\) 而出錯。不推薦。

// 2. path.join() - 跨平台安全 (推薦!)
console.log(path.join('/A/B/C', '/fetch_emp'));
// 輸出 (Windows): \A\B\C\fetch_emp
// 輸出 (Linux/macOS): /A/B/C/fetch_emp  (與上面相同,但更可靠)
// 解釋:  path.join() 根據 *當前作業系統* 自動選擇正確的分隔符。這是最安全、可靠的方法,確保跨平台兼容。

// 3. path.posix.join() - 單純傳入virtualDirPath
console.log(path.posix.join('/A/B/C'));
// 輸出 (所有系統): /A/B/C
// 解釋: 只傳入一個參數時,作用等同於 path.normalize(),用來規範化路徑。

// 4. path.posix.join() + ./  (當前目錄)
console.log(path.posix.join('/A/B/C', './'));
// 輸出 (所有系統): /A/B/C/
// 解釋:  path.posix.join() 強制使用 POSIX 分隔符 (/)。'./' 代表當前目錄,被絕對路徑 /A/B/C 吸收,最後加上/。

// 5. path.posix.join() + / (根目錄)
console.log(path.posix.join('/A/B/C', '/'));
// 輸出 (所有系統): /A/B/C/
// 解釋:  path.posix 使用 / 分隔符。'/' 代表根目錄。因 /A/B/C 已是完整路徑, 最終結果為/A/B/C加上/,並處理掉多餘的 /

// 6. path.posix.join() + /fetch_emp (強制 POSIX)
console.log(path.posix.join('/A/B/C', '/fetch_emp'));
// 輸出 (所有系統): /A/B/C/fetch_emp
// 解釋:  path.posix.join() 強制 / 分隔符。即使 '/fetch_emp' 開頭是 /,也會正確處理,避免雙斜線。

2025/02/21

React 19 use API 避坑指南:常見錯誤與解決方案 (含 RCC 說明)

作者:吳祐賓 

 

 


 

 

 

React 應用程式開發過程中,很常需要去 Server 端拉資料回來,通常我們會使用 useState, useEffect 來完成工作。

 

底下是一個簡單的範例,從 JSONPlaceholder (著名的公開偽資料 API 服務)提供的 API 取得使用者資料。

 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import React, { useState, useEffect } from 'https://esm.sh/react@18'; // Changed to React 18 for compatibility with useEffect/useState
import ReactDOMClient from 'https://esm.sh/react-dom@18/client'; // Changed to React 18

const Users = () => {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null); // Added error handling

  useEffect(() => {
    const fetchUsers = async () => {
      const res = await fetch('https://jsonplaceholder.typicode.com/users');
      const data = await res.json();
      setUsers(data);
      setLoading(false);
    };

    fetchUsers();
  }, []); // Empty dependency array ensures this runs only once on mount

  if (loading) {
    return <h1>Loading...</h1>;
  }

  if (error) {
    return <h1>Error: {error.message}</h1>; // Display the error message
  }

  return (
    <ul>
      {users.map((user) => (
        <div key={user.id}>
          <h2>{user.name}</h2>
        </div>
      ))}
    </ul>
  );
};


function App() {
  return (
    // Suspense is not needed with useEffect + useState for initial data loading
    <Users />
  );
}

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

 

 

useEffect + fetch 坊間有很多教學,因為很多人會遇到 "Warning"

 

這個設計法已經行之有年,還是有很多人搞不清楚 useEffect + fetch 為什麼會出現類似 "Warning: useEffect function must return a cleanup function or nothing. Promises..." 的訊息。

 

解決方式很多,React 19 也提出了一個解決方式。

 

 

 use API

 

use API 是 React 19 提出的新概念,是搭配 React Server Components 框架,如 Next.js 等設計使用。

 

use 的設計目標是:

 

  • 簡化 Server Components 中的資料取得設計
  • 更簡潔的程式碼和更高的可讀性
  • 錯誤處理更方便 (Error Boundaries 整合)

 

 

直接將 React 18 程式碼升級到 React 19,參考 How to fetch an API with REACT 19 | use - suspense 文章,程式碼如下:

 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import React, { Suspense, use } from 'https://esm.sh/react@19';
import ReactDOMClient from 'https://esm.sh/react-dom@19/client';
const fetchUsers = async () => {
  const res = await 
  fetch('https://jsonplaceholder.typicode.com/users');
  return res.json();
};


const Users = () => {
  const users = use(fetchUsers());

  return (
    <ul>
      {users.map((user) => (
        <div key={user.id}>
          <h2>{user.name}</h2>
        </div>
      ))}
    </ul>
  );
};


function App() {
  return (
    <Suspense fallback={<h1>Loading...</h1>}>
      <Users />
    </Suspense>
  );
}

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

 

 

程式碼就是如此簡單,但 React Client Components 時會無限 Promise

 

React Stand-alone 就是 RCC 模式,在這個模式下使用 use API,就會出現如下圖般無限 Promise 的情形。

 

因為 use 是搭配 RSC 的設計,所以和 render 會有關係,RSC 框架會管理好 use 裡 Promise 的狀態,但 RCC 沒有框架處理 Promise 狀態,流程上是:

 

User render -> use(new Promise) <in use> -> do Promise -> retrun New Promise <in use> -> "use" call User render... repeat 

 

 


 

 

 RCC 的 Promise 管理快取機制自己寫,就為了 use API

 

這部份和 useCallback 有點像,只有 callback 相依的變數修改了才會更新。Promise 也可以如法泡製。參考 New React 19 use hook–deep dive 的教學,寫了 useQuery 來模擬 Server 端對 Promise 快取機制的處理。

 

程式碼修改如下。

 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import React, { Suspense, use, useState } from 'https://esm.sh/react@19';
import ReactDOMClient from 'https://esm.sh/react-dom@19/client';

/** useQuery Hook */
const promiseCache = new Map();

const useQuery = ({ fn, key }) => {
  if (!promiseCache.has(key)) {
    promiseCache.set(key, fn());
  }

  const promise = promiseCache.get(key);

  const result = use(promise);

  return result;
};
/* end useQuery Hook */

const fetchUsers = () => {
  const res = fetch('https://jsonplaceholder.typicode.com/users')
    .then( response => response.json());
  return res;
};

const Users = () => {
  const users = useQuery({fn: ()=> fetchUsers(), key: "fetchUsers"});

  return (
    <ul>
      {users.map((user) => (
        <div key={user.id}>
          <h2>{user.name}</h2>
        </div>
      ))}
    </ul>
  );
};


function App() {
  return (
    <Suspense fallback={<h1>Loading...</h1>}>
      <Users />
    </Suspense>
  );
}

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

 

 

 

重點總結:


傳統方式 (React 18 及之前): 使用 useState 和 useEffect 組合來處理資料獲取、載入狀態和錯誤處理。  這是常見且有效的方法,但程式碼相對較多。



React 19 的 use API:


  • 設計目標: 簡化資料獲取,特別是在 Server Components (RSC) 環境下,提供更簡潔的程式碼和內建的錯誤處理。
  • RSC 環境: use hook 旨在與 React Server Components 框架 (如 Next.js) 搭配使用。 框架會負責管理 Promise 的狀態,避免無限迴圈。
  • RCC (React Client Components) 環境限制: 在獨立的 React Client Components (沒有 RSC 框架) 中直接使用 use 會導致無限迴圈,因為沒有機制來管理 Promise 的狀態。


模擬 RSC 的 Promise 快取 (RCC 環境):

 

  • 為了在 RCC 環境下也能體驗 use 的簡潔性,可以自行建立一個 Promise 快取機制 (例如範例中的 useQuery hook)。
  • useQuery hook 使用 Map 來儲存 Promise,確保相同的請求只會發送一次,避免重複的非同步操作。


 

總之,use API 是 React 19 中一個強大的新特性,但目前主要適用於 Server Components 環境。對於傳統的 Client Components,就繼續使用 useEffect 和 useState,對使用新技術有興趣的人,可以自行實作 Promise 快取機制來模擬 use 的行為。 

 

如果你正在使用 Next.js 這樣的 RSC 框架,use API 可以大幅簡化你的資料 request 的程式碼。

 

和你分享。

 

See Also

 

2025/02/19

React 19 + esm.sh CDN 新手入門:快速掌握 React 19 最新功能

作者:吳祐賓

 

 


 

 

前言


React 19 帶來了許多令人興奮的新功能,例如 Actions, Server Components, Asset Loading 等,可以幫助我們提升開發效率和應用程式效能。

 

本教學將以新手友善的方式,帶領大家快速入門 React 19,並搭配 esm.sh CDN 快速搭建開發環境,讓你立即體驗 React 最新功能!


附帶說明,本教學只會提及 Client-Side Rendering (CSR) 功能,如果這篇文章迴響不錯,會再另外推出 Server-Side Rendering (SSR) 的教學內容,敬請期待!


準備工作


要開始 React 19 開發,最快速的方式就是使用 esm.sh CDN。它讓我們無需安裝 Node.js 和 npm,就能在瀏覽器中直接使用 React 19。細節可以參閱我之前寫的:我在 React 19 新手入門:CDN + esm.sh 快速上手

 

首先,建立一個 HTML 檔案 (例如 index.html),並加入以下程式碼:

 

<!doctype html>
<html>
<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <script type="importmap">
    {
      "imports": {
        "react": "https://esm.sh/react@19",
        "react-dom/client": "https://esm.sh/react-dom@19/client"
      }
    }
  </script>
  <script type="module" src="https://esm.sh/tsx"></script>
  <title>React App with JSX React 19</title>
</head>
<body>
<div id="root"></div>

<script type="text/babel">
  import { createRoot } from "react-dom/client"  

  function App(){
    return <>
      <h1>Hello, React 19 with ESM.sh</h1>
    </>    
  }

  createRoot(root).render(<App />)
</script>
</body>
</html>

 

這個簡單的 HTML 檔案就建立了一個 React 19 開發環境。esm.sh 使用 `esm.sh/tsx`,用來啟動 JSX 即時編譯的能力。在開發模式 (development mode)下非常好用。

 

我還是習慣使用 CDN 做教學範例。現在,你可以直接用瀏覽器打開 index.html,看到 "Hello, React 19 with ESM.sh!" 的訊息,就代表成功了!

 

 

React 19 新功能巡禮

 

接下來,我們將逐一介紹 React 19 的重點新功能,讓你快速掌握 React 最新技術。

Actions:簡化資料流程與狀態管理


在 React 18 之前,處理表單提交或資料更新時,我們需要手動管理 loading 狀態和錯誤處理。

 

React 19 引入了 Actions (useTranstition),可以更簡潔地處理表單的非同步操作。

 

簡單的說,React 19 Actions 比較接近資料庫的交易概念,我們拿官方的 18/19 的程式碼來比較,並附上我修改後的程式碼。

 


 

 

<!doctype html> <html> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> <title>React App with JSX React 19</title> </head> <body class="dx-viewport"> <div id="root"></div> <script type="text/babel" data-type="module"> import React, {useState, useTransition} from "https://esm.sh/react@19" import ReactDOMClient from "https://esm.sh/react-dom@19/client" // Using pending state from Actions function UpdateName({}) { const [name, setName] = useState(""); const [error, setError] = useState(null); const [isPending, startTransition] = useTransition(); async function updateName(name) { // 模擬異步操作,例如 API 呼叫 return new Promise((resolve) => { setTimeout(() => { if (!name) { resolve("Name cannot be empty."); // 模擬錯誤情況 } else if (name.length > 20) { resolve("Name is too long."); // 模擬另一個錯誤情況 } else { // 在此處添加更新名稱的實際邏輯,例如 API 呼叫 console.log("Name updated to:", name); // 模擬成功情況 resolve(null); // 成功時返回 null 表示沒有錯誤 } }, 1000); // 模擬 1 秒的延遲 }); } const handleSubmit = () => { startTransition(async () => { const error = await updateName(name); if (error) { setError(error); return; } setError("Ok!"); }) }; return ( <div> <input value={name} onChange={(event) => setName(event.target.value)} /> <button onClick={handleSubmit} disabled={isPending}> Update </button> {error && <p>{error}</p>} </div> ); } function App(){ return <> <h1>Hello, React 19 with ESM.sh</h1> <UpdateName /> </> } const root = ReactDOMClient.createRoot(document.getElementById('root')) root.render(<App />) </script> </body> </html>

 

 

使用 form 和 useActionState,使程式更簡單

 

前面的例子是使用 Button 直接送出 request 進行狀態切換。React 19 對 form 也加入了一些 Action。修改後程式碼如下。可以看到 useActionState 內可以自動切換狀態,並且和錯誤機制配合的冾到好處。

 

<!doctype html>
<html>
<head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
    <title>React App with JSX React 19</title>
</head>
<body class="dx-viewport">
<div id="root"></div>

<script type="text/babel" data-type="module">
  import React, {useState, useActionState} from "https://esm.sh/react@19"
  import ReactDOMClient from "https://esm.sh/react-dom@19/client" 


  async function updateName(name) {
      // 模擬異步操作,例如 API 呼叫
      return new Promise((resolve) => {
        setTimeout(() => {
          if (!name) {
            resolve("Name cannot be empty."); // 模擬錯誤情況
          } else if (name.length > 20) {
            resolve("Name is too long."); // 模擬另一個錯誤情況
          }
          else {
            // 在此處添加更新名稱的實際邏輯,例如 API 呼叫
            console.log("Name updated to:", name); // 模擬成功情況
            resolve(null); // 成功時返回 null 表示沒有錯誤
          }
        }, 1000); // 模擬 1 秒的延遲
      });
    }


    // Using <form> Actions and useActionState
    function ChangeName({ name, setName }) {
      const [error, submitAction, isPending] = useActionState(
      async (previousState, formData) => {
        const error = await updateName(formData.get("name"));
        if (error) {
          return error;
        }
        return null;
      },
      null,
    );

    return (
      <form action={submitAction}>
        <input type="text" name="name" />
        <button type="submit" disabled={isPending}>Update</button>
        {error && <p>{error}</p>}
      </form>
    );
  }

  function App(){
    return <>
      <h1>Hello, React 19 with ESM.sh</h1>
      <ChangeName />
    </>    
  }

  const root = ReactDOMClient.createRoot(document.getElementById('root'))
  root.render(<App />)
</script>
</body>
</html>

 

 

New API:  use - 讓 Async Function 回傳 Promise 更融入 React 世界


在 React 19 之前,我們在 Component 裡處理 Async Function 的回傳值時,通常需要 useState, useEffect 搭配 async/await 才能比較好的處理 loading, error, data 狀態。

 

這在 fetch 資料庫經常會希望有但都要自己來刻的功能。


React 19 引入了 use 這個新的 Hook,讓你在 React Component 裡可以直接 "await" Promise,讓 Async Function 的回傳值可以更自然的融入 React 的世界。

我們來看一個簡單的例子。

 

// New API: use function fetchData() { console.log('Fetching data...'); return new Promise(resolve => { setTimeout(() => { console.log('Data fetched!'); resolve({ message: "Data from Async Function!" }); }, 1500); // 模擬 1.5 秒的 API 延遲 }); } function DataDisplay() { // 直接 use(Promise) const data = use(fetchData()); return ( <div> <p>Data Display:</p> {data ? <p>{data.message}</p> : <p>Loading data...</p>} </div> ); }

 

在 DataDisplay 這個 Component 裡,我們定義了一個 fetchData 的 Async Function,這個 Function 模擬了一個 API 呼叫,會在 1.5 秒後 resolve 一個包含 message 的 Object。

 

重點在 DataDisplay Component 裡,我們可以直接使用 use(fetchData()),use Hook 會處理 fetchData() 回傳的 Promise

 

Pending 狀態: 在 Promise resolve 之前,Component 會進入 Pending 狀態 (Suspense),你可以看到畫面上顯示 "Loading data..."。
    成功狀態: 當 Promise resolve 後,use(fetchData()) 會回傳 Promise 的 resolve 值,也就是 { message: "Data from Async Function!" },Component 重新 render,畫面就會顯示 "Data from Async Function!"。

 

use 的優點:


  • 更簡潔的 Async Function 處理: 不需要再手動管理 loading 狀態,程式碼更簡潔易讀。
  • 提升開發體驗: 讓 Async Function 更自然的融入 React Component 的開發流程。
  • 搭配 Suspense: 可以和 Suspense Component 搭配使用,讓 Loading 體驗更流暢。


 

Ref as a prop - Function Component 也可以直接接收 Ref 了!


在 React 19 之前,Function Component  如果需要接收 Ref,必須要透過 forwardRef 這個 API 包裝才能使用。


// React 18 寫法
const MyInput = React.forwardRef((props, ref) => {
  return <input placeholder={props.placeholder} ref={ref} />;
})

 

// React 19 寫法 - Function Component 直接接收 ref prop
function MyInput({placeholder, ref}) {
  return <input placeholder={placeholder} ref={ref} />
}

 

React 18 寫法對於 Function Component 來說,多了一層 forwardRef 的包裝,語法上比較囉嗦一點,也讓 Function Component 的程式碼看起來比較複雜。

 

React 19  簡化了 Function Component  Ref 的使用方式。現在,Function Component  可以像 Class Component 一樣,直接接收 ref 這個 Prop 了!

 

未來 forwardRef 也將棄用,請盡早學會 ref as a prop。

 

實際案例會像這樣:

 

// Ref as a prop
  function MyInput({placeholder, ref}) {
    return <input placeholder={placeholder} ref={ref} />
  }

  function RefInputComponent() {
    const inputRef = useRef(null);

    useEffect(() => {
      if (inputRef.current) {
        console.log('Input Ref:', inputRef.current);
      }
    }, []);

    return (
      <div>
        <p>Ref as a prop:</p>
        <MyInput placeholder="Enter text" ref={inputRef} />
      </div>
    );
  }

 

總結

 

好的,React 19 這次更新真的太棒啦!一口氣帶來了 Actions, use Hook, ref as a prop 這些超實用的新功能,每一個都打中開發者的痛點,讓開發 React 應用程式變得更輕鬆、更高效。

 

總結一下 React 19 這些必學新功能:

  • Actions:  告別複雜的表單狀態管理!useTransition 和 useActionState 就像是神隊友,幫你優雅地處理非同步操作, loading 狀態、錯誤處理都變得超簡單,程式碼也更簡潔易讀,真的就像資料庫交易一樣方便!


  • use Hook:  Async Function 的救星!以前在 Component 裡處理 Promise,總是要 useState, useEffect 寫一堆,現在有了 use,直接 await 就搞定,程式碼瞬間清爽!資料載入 loading 狀態也自動處理,開發體驗直接起飛!


  • ref as a prop:  Function Component 的 Ref 也太方便了吧!再也不用 forwardRef 包裝了,直接像 Class Component 一樣接收 ref prop 就行,語法更直覺,程式碼也更簡潔,而且聽說 forwardRef 以後要被棄用,這個一定要學起來!


 

React 19 這些新功能,不只是錦上添花,更是實實在在地提升了開發效率和體驗。無論你是 React 新手還是老手,都非常建議趕快升級到 React 19,體驗這些新功能帶來的魅力!

 

看完這篇入門教學,是不是覺得 React 19 其實也沒那麼難?趕快動手試試看,用 esm.sh CDN 快速搭建你的 React 19 開發環境

 

一起擁抱 React 新時代吧!  相信你會愛上 React 19 的!
 

 

 

See also