2021/11/10

React Router 從 v4, v5 升級 v6 的異動


Router v6 的改版所變動之處非常大,許多舊語法、屬性被【移除】,可以說是新產品也不為過,雖然作者有提出未來可能會提供向下相容的語法,但目前為止仍舊沒有出現,我想再回去的機率很低,還是接受現實改為新版語法吧!

withReouter 已移除

withReouter 已移除,改用 Router Hooks 取代,這意味著以前使用 class 所開發的元件將無法繼續使用,改為函式元件似乎是條不得不為的道路。

<Route /> path 移除多路徑陣列寫法

 <Route path=["/list", "/list/:id"] /> 寫法在 v6 已不可用,作者說 Router v6 引擎有針對多路由做最佳化,使用多次宣告 Route 很直覺,效能更好。

<Route /> exact、componet、render 屬性移除

exact 屬性移除,前面提到作者有對 Router v6 引擎最佳化,故不再需要 exact 絕對路徑屬性。

Route 的 Component、Render 屬性移除,改以 element 取代,傳參數的方式改為 useParams 等 Hooks 後就不再需要以前的 Component、Render 屬性,寫法可以一致化。

Link, NavLink 移除部份屬性

activeClassName、activeStyle 被移除,改以 Style 屬性取代,這裡官方手冊有個地方範例程式碼寫錯:

而在說明整合的文件中有寫出正確的內容:


範例使用 className 說明,這裡也提到 style 寫法相同,但就僅止於此,一不小心會花很多時間在找問題上。


還是可以寫得出範列所要傳達的效果,請放心。

總結

Router v6 改動的地方非常多,現在書和網路上許多資料還停留在 v4、v5 上,在使用 v6 時常會用到已移除的功能,寫起來倍感艱辛。

改版的過程中會發現其實 v6 改動方向是把路由工作劃分得很清楚,Route 歸 Route;Link 歸 Link,Element 歸 Element,寫作上比較不會有串來串去的情形。

整體來說 v6 的改動是利大於弊,只要你能熬過那升級陣痛期的話。

和你分享 😉

See also

2021/10/15

JS DataSnap framework in Webpack

 


Webpack 5 初探筆記】這篇記錄了 Webpack 三項重點:

  • JS 入口
  • HTML Page 建立及嵌入 JS 設定。
  • 除錯模式

你以為已經很多了?不,還有線上除錯服務器 (Webpack devServer) 還沒用到,該篇文章僅就編譯功能處理,編譯完成後再到實機裡除錯。

所以本篇就來研究如何讓 Webpack devServer 和後端 WebAPI 結合,本篇文章會講到兩個重點:

  • 使用 devServer Proxy 來解決【跨來源資源共用(CORS)】問題。
  • JS DataSnap framework 修改呼叫 DataSnap API 的方式。

使用 devServer Proxy 來解決【跨來源資源共用(CORS)】問題

devServer 的基本功能是提供網頁服務,熱編譯重讀 (hot reload) 功能則是在前端網頁開發時省時好幫手,檔案一儲存畫面就立即更新,這能省下許多重新編譯的時間,所以這次就把使用它為目標。

webpack 設定 devServer 非常簡單,內容如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
module.exports = {
  ...
    devServer: {
        static: {
            directory: path.join(__dirname, 'dist'),
        },
        hot: true,
        port: 9000,
    }
  ...
}

接著在 index.js 裡寫上範例程式碼:

1
2
3
setConnection("localhost", "8080", "")
var oldExecutor = new ServerFunctionExecutor("TServerMethods1", connectionInfo);
console.log("EchoString : ", oldExecutor.executeMethod("EchoString", "GET", ["A B C"]))

網頁在啟動後會出現錯誤:


已封鎖跨來源請求: 同源政策不允許讀取】這句話的翻譯就是:後端網頁伺服器未對 CORS 做出對應的處理,在這裡指的便是 DataSnap 伺服器。

Eden 的【Delphi in Depth DataSnap 網站應用程式全端開發】一書中所提到的 CORS 解決方案其實就是要解決前端伺服器呼叫 DataSnap 伺服器上的 API 所產生的問題,有興趣的開發者邀請你購買起來看,保證物超所值!

devServer 對 CORS 的處理方式:Proxy

來看看 Webpack 官網對 Proxy 是怎麼寫的:

当拥有单独的 API 后端开发服务器并且希望在同一域上发送 API 请求时,代理某些 URL 可能会很有用。

「擁有單獨的 API 後端開發服務器」以本篇指的就是 DataSnap 伺服器,所以按照官網的範例調整後,webpack.config.js 內容為:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
    devServer: {
        static: {
            directory: path.join(__dirname, 'dist'),
        },
        hot: true,
        port: 9000,
        proxy:{
            "/datasnap/rest":"http://localhost:8080"
        }
    },

現在,http://localhost:9000/datasnap/rest 的網址就會對應到 http://localhost:8080/datasnap/rest,完成後就來試一下:


設定成功,現在可以順利在開發時期輕鬆呼叫 DataSnap API! 😉


JS DataSnap framework 修改呼叫 DataSnap API 的方式

我在【JavaScript ES6 call DataSnap API with Promise Fetch】寫到使用 fetch 來取代 ServerFunctionExecutor.executeMethodUrl 裡的 XMLHttpRequest,但為了避免回呼地獄 (callback hell) 而放棄 ServerFunctionExecutor 整個類別,必須要說,我真的很喜歡 EMBT 寫的 ServerFunctionExecutor 類別,它不僅是我學習 JavaScript 的啟蒙,而且對瀏覽器相容性也達到 100%,連 IE6 都相容,我真的愛死 ServerFunctionExecutor 類別了!

修改 ServerFunctionExecutor.js 原始碼是一種方法,但每一版的 Delphi 對 ServerFunctionExecutor 類別都有或多或少的修改,我認為最好的方案就是【繼承】,既然要用 fetch,也表示要放棄對 IE 的相容性,那採用 JavaScript ES6/7 新標準也是可以的,程式碼如下:

  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
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
import "./dsjs/connection"
import "./dsjs/ServerFunctionExecutor"

class DSFunctionExecutor extends ServerFunctionExecutor {
    constructor(className, connectionInfo, owner) {
        super(className, connectionInfo, owner)
    }

    /**
     * This function executes the given method with the specified parameters and then
     * notifies the Promise when a response is received.
     * @param url the url to invoke
     * @param contentParam the parameter to pass through the content of the request (or null)
     * @param requestType must be one of: GET, POST, PUT, DELETE
     * @param hasResult true if a result from the server call is expected, false to ignore any result returned.
     *                  This is an optional parameter and defaults to 'true'
     * @param accept The string value to set for the Accept header of the HTTP request, or null to set as application/json
     * @return This function will return the result that would have otherwise been passed to the Promise.
     */
    async #fetchMethodURL(url, contentParam, requestType, hasResult, accept) {
        if (hasResult == null) hasResult = true;

        requestType = validateRequestType(requestType);

        const fetchHeaders = new Headers();
        fetchHeaders.append("Accept", (accept == null ? "application/json" : accept));
        fetchHeaders.append("Content-Type", "text/plain;charset=UTF-8");
        fetchHeaders.append("If-Modified-Since", "Mon, 1 Oct 1990 05:00:00 GMT");
        const sessId = getSessionID();
        if (sessId != null)
            fetchHeaders.append("Pragma", "dssession=" + sessId);
        if (this.authentication != null)
            fetchHeaders.append("Authorization", "Basic " + this.authentication);

        const fetchParams = {
            method: requestType,
            body: contentParam,
            headers: fetchHeaders,
        }
        try {
            const response = await fetch(url, fetchParams)
            this.#parseFetchSessionID(response);
            const responseText = await response.text();
            let JSONResultWrapper = null;
            try {
                JSONResultWrapper = JSON.parse(responseText);
            }
            catch (e) {
                JSONResultWrapper = responseText;
            }
            if (response.status == 403) {
                if (JSONResultWrapper != null && JSONResultWrapper.SessionExpired != null) {
                    //the session is no longer valid, so clear the stored session ID
                    //a new session will be creates the next time the user invokes a server function
                    setSessionData(null, null);
                }
            }
            //all other results (including other errors)
            //return JSONResultWrapper;
            const returnObject = JSONResultWrapper;
            if (returnObject != null && returnObject.result != null && Array.isArray(returnObject.result)) {
                return returnObject.result[0];
            }
            return returnObject;
        }
        catch (err) {
            console.error('Error:', err)
            return err
        }
    };

    /**
     * This function executes the given method with the specified parameters and then
     * notifies the callback when a response is received.
     * @param methodName the name of the method in the class to invoke
     * @param requestType must be one of: GET, POST, PUT, DELETE
     * @param params an array of parameter values to pass into the method, or a single parameter value
     * @param hasResult true if a result from the server call is expected, false to ignore any result returned.
     *                  This is an optional parameter and defaults to 'true'
     * @param requestFilters JSON Object containing pairs of key/value filters to add to the request (filters such as ss.r, for example.)
     * @param accept The string value to set for the Accept header of the HTTP request, or null to set application/json
     * @return This function will return the result that would have otherwise been passed to the Promise.
     */
    async fetchMethod(methodName, requestType, params, hasResult, requestFilters, accept) {
        const url = this.getMethodURL(methodName, requestType, params, requestFilters);
        return await this.#fetchMethodURL(url[0], url[1], requestType, hasResult, accept);
    };

    /**
     * Tries to get the session ID from the Pragma header field of the given request/response object
     * If successful, will set the value of the $$SessionID$$ and $$SessionExpires$$ variables accordingly.
     * @param response the response from the http request
     */
    #parseFetchSessionID(response) {
        if (response != null) {
            //pragma may store the Session ID value to use in future calls
            var pragmaStr = response.headers.get("Pragma");

            if (pragmaStr != null) {
                //Header looks like this, if set: Pragma: dssession=$$SessionID$$,dssessionexpires=$$SessionExpires$$
                var sessKey = "dssession=";
                var expireKey = "dssessionexpires=";
                var sessIndx = pragmaStr.indexOf("dssession=");

                if (sessIndx > -1) {
                    var commaIndx = pragmaStr.indexOf(",", sessIndx);
                    commaIndx = commaIndx < 0 ? pragmaStr.length : commaIndx;
                    sessIndx = sessIndx + sessKey.length;
                    var sessionId = pragmaStr.substr(sessIndx, (commaIndx - sessIndx));

                    var sessionExpires = null;
                    var expiresIndx = pragmaStr.indexOf(expireKey);
                    if (expiresIndx > -1) {
                        commaIndx = pragmaStr.indexOf(",", expiresIndx);
                        commaIndx = commaIndx < 0 ? pragmaStr.length : commaIndx;
                        expiresIndx = expiresIndx + expireKey.length;
                        var expiresMillis = parseInt(pragmaStr.substr(expiresIndx, (commaIndx - expiresIndx)));
                        if (expiresMillis != 0 && expiresMillis != NaN) {
                            sessionExpires = new Date();
                            sessionExpires.setMilliseconds(sessionExpires.getMilliseconds() + expiresMillis);
                        }
                    }

                    setSessionData(sessionId, sessionExpires);
                }
            }
        }
    }
}

程式碼說明

因 fetch 回傳的 response 和 XMLHttpRequest 的不同,所以抄了原來的 executeMethodURL、 parseSessionID 為 #fetchMethodURL 和 #parseFetchSessionID,加了「#」是為私有屬性,以避免免被其它開發者誤用。

應用程式大多需要同步處理以得到較好的操作性,故使用 async / await 等待 response 回來。

修改和執行結果如下:

這裡要留意 Webpack Babel 外掛在編譯這類別的時候可能會出現【regeneratorRuntime is not defined】錯誤訊息,原因很複雜,【Webpack 前端打包工具 - 使用 babel-loader 編譯並轉換 ES6+ 代碼 】提供了說明,而解決方式很簡單:

安裝 Babel transform-runtime 外掛:

npm install --save-dev @babel/plugin-transform-runtime
npm install --save @babel/runtime
//babel.config.json


{
"presets": ["@babel/preset-env"], "plugins": ["@babel/transform-runtime"]
}


本次 npm 用到的指令

npm init
npm i webpack -D
npm install --save-dev css-loader
npm install --save-dev style-loader
npm install --save-dev html-webpack-plugin
npm install --save-dev mini-css-extract-plugin
npm install -D babel-loader @babel/core @babel/preset-env webpack
npm install --save-dev clean-webpack-plugin
npm install copy-webpack-plugin --save-dev
// 排除 ES6 太新語法造成 "Babel 7 - ReferenceError: regeneratorRuntime is not defined"
// https://stackoverflow.com/questions/53558916/babel-7-referenceerror-regeneratorruntime-is-not-defined
npm install --save-dev @babel/plugin-transform-runtime
npm install --save @babel/runtime

結論時間

透過這次 Webpack 的練習,發現只要 JS 該引用的單元都正確引用,Visual Studio Code 都能夠正確追蹤到程式碼來源或 JS DOC,尤其是在追蹤 JS DataSnap framework,VS Code 真的有效加強學習的效果,向你推薦!

本次練習專案我放在 Github 上,連結在 See also 區,有興趣的朋友歡迎來看看。 😉


See also

2021/10/08

Webpack 5 初探筆記

 


在寫完【還在 React 從入門到放棄?告訴你選擇比努力還重要的事】這篇文章之後,原本要繼續我的 React 裸奔之旅,但看到【【前端速成】Webpack5 快速入門|Tiktok工程師帶你入門前端|布魯斯前端】的直播內容,發現 Webpack 和 Delphi 的專案檔實在太像了,手癢忍不住動手把自己的 jQuery 網站利用 Webpack 重新打包順道重構,過程很有趣,而且整理後的專案內容就像是看網站地圖一樣,看了心情大好。

趁還有記憶時把 Webpack 的參數檔和 NPM package 參數檔透過這偏文章寫下來。

Package.json

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// package.json
{
  "name": "eden_first_project",
  "version": "1.0.0",
  "description": "",
  "main": "maindesk.js",
  "scripts": {
    "build": "SET NODE_ENV=production && webpack",
    "design_build": "SET NODE_ENV=development && webpack && copyfile_with_debug.bat",
    "dev": "webpack serve"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.15.5",
    "@babel/preset-env": "^7.15.6",
    "babel-loader": "^8.2.2",
    "clean-webpack-plugin": "^4.0.0",
    "html-webpack-plugin": "^5.3.2",
    "webpack": "^5.56.1",
    "webpack-cli": "^4.8.0",
    "webpack-dev-server": "^4.3.1"
  }
}

程式說明

devDependencies:內容是 npm 安裝的外掛工具及版本,功能有:

  • webpack 本體
  • webpack 外掛 -- 清理編譯後檔案
  • webpack 外掛 -- 頁面樣板設定
  • babel -- 轉譯 ES6 程式碼為其它瀏覽器可讀的程式碼內容

 

scripts:在命令提示字元視窗下執行 webpack 的指令,語法為【npm run [scripts]】,SET 是設定 Windows 的環境變數,可以依照【設計】或【產品】語法來改變 webpack 封裝設定。[&&] 是 Windows 執行多指令的節點,某些靜態檔案還可以透過批次檔進行複製。

npm 在對專案處理時都會以 package.json 作為進入點,比較要記的就是這兩個屬性。


webpack.config.js - part 1

程式說明

package.json 的 SET NODE_ENV,它會在 webpack.config.js 的 process.env 被 NPM 寫入環境變數。

先快速導覽大分類,之後再一個個進去看。

module.export 這是 webpack 專案檔的核心。

mode 設定【設計】或【產品】模式;entry 要被 webpack 編譯的 js 檔案。

output 指定原檔案編譯後存放的路徑和命名規則。

devServer 執行 webpack server 時 Web 伺服器的參數。

module webpack 在讀取 js 檔以外的檔案時,所需要的 loader

devtool 編譯時做對照檔 (map),使瀏覽器能夠讀出編譯和原始程式碼的對照檔

plugin webpack 外掛設定區。 

webpack.config.js - part 2


程式說明

entry 裡,要寫上所有需要編譯的 js 檔案,格式為【暱稱:路徑】,暱稱在之後的【plugins 》 HtmlWebpackPlugin】會用到。

output 裡,path 說明 entry 裡的檔案在編譯後所放置的路徑,以 package.json 所在路徑為 root,可以自由指定,filename 為了確保客戶端每次都會重新讀取,可以把檔案加入【hash】參數,編譯會將 hash 碼帶入檔名,如:maindesk.f22745d9520340e7af08.bundle.js。


webpack.config.js - part3


程式說明

plugin 裡面主要放 webpack 外掛模組,有:

CleanWebpackPlugin 是每次編譯當下都會將目的目錄內的檔案全數清空。

webpack.DefinePlugin 是定義編譯環境的全域變數,此例可以在網路中的任何 JS 寫上類似以下的內容:

可以在編譯時決定要執行哪部份的程式內容,好用!

HtmlWebpackPlugin 決定讀取的樣板和 HTML 檔案路徑,chunks 是決定要寫入 entry 的哪些檔案,以暱稱設定,設為空字串時則不匯入任何 js 檔;也可以使用 excludeChunks 決定不加入的 js 清單。


結論時間

學習 Webpack 的過程中不斷帶入以前寫 Delphi 專案檔的時光,所以還蠻能理解 Webpack 的各項設定的內容,都已經用編譯了,那就順便把 Delphi 的設定概念套入,Webpack 還有很多細節的設定,只要大方向有了,再往細節裡找的難度就會下降很多!

今日成就:把 jQuery 網站套入 Webpack 專案編譯成功!


和你分享 😉

2021/10/04

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


看了許多 React 的入門資料,大多會是從 create-react-app 專案起始,用久了覺得很不舒服,原因如下:

NPM 編譯經常無預期出錯

NPM 編譯經常無預期出錯,學習 React 免不了會經過一連串試錯的過程,但往往會不經意的出現許多編譯錯誤,大多是 Webpack 出現問題,在無法排除錯誤的情形下,最常做的就是重建 node_modules 內容,次數一多,是在學習 React 還是背誦 NPM i 指令,我已經搞不清楚了。

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

只要不是靜態網頁,在前端的開發過程一定會和後端 API 傳遞訊息,尤其是開發網頁應用程式更是必須。在設計時期,只要前端和後端的 Web 服務網域不同,就一定會遇上【跨來源資源共用(Cross-Origin Resource Sharing (CORS))】這個大魔王,後端就必須進行 CORS 設定,但實際上線又會是在同一網域,只有開發時期才會遇到的 CORS 顯得是非常詭異的歷程。

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

2021/09/17

逐步使用 React 時,元件如何和外部 DOM 連結?

已知 React 會渲染指定的 div,學習資料中似乎沒有和外部 DOM 連結的方法?

React 元件渲染的條件是 props 或 state 變更時觸發,所以把 DOM 元素放入到 React 元件的 Props,就能夠滿足此條件,範例程式碼如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const e = React.createElement;

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.props.textBox.onchange = () => this.calcCallback();
    this.state = { text: "" };
  }

  calcCallback() {
    this.setState((state, props) => ({
      text: this.props.textBox.value
    }));
  }

  render() {
    return e(React.Fragment, null, e("div", null, this.state.text));
  }
}

ReactDOM.render(
  e(MyComponent, { textBox: document.querySelector("#textBox1") }, null),
  document.querySelector("#app2")
);

HTML

1
2
<input id="textBox1" value="Hello Vanilla"></input>
<div id="app2"></div>

 

成果圖



 

 

 

 

2021/09/16

愛用 jQuery 又想擁抱 React 的逐步採用練習,以 DevExtreme 為例

React 逐步採用

不知道你是否和我一樣,斷斷續續學幾年的 React,總是無法順利把 React 加入到現有的 jQuery 網站中,學 jQuery 時很快樂,需要什麼功能時只需要在 script 標籤裡信手捻來 js 檔案就能快速使用,怎麼到了 React 就完全走鐘?非得使用 NodeJS 編譯才可以?React 起手式就是 create-react-app?這其中一定有什麼誤會,帶你來看看官方說法:

React 在剛推出的時候就容許被逐步採用,你可以按自己所需可多可少的採用 React。 或許你只想在現存的網頁上增加少量的互動性。採用 React component 會是個非常好的選擇。 -- React 官方手冊

按照官網裡的步驟練習結束,就如上面所說一樣,可以很順利的把 React 加入不需 NodeJS 編譯的傳統網站裡。

造成網頁變慢的元兇

會學習到可選的 JSX,JSX 很有意思,可以在 JS 裡安插類 HTML 標籤,從【逐步採用】學習到【可選:嘗試 React 與 JSX】的最後一段結論:

這種方式最適合用來學習和建立簡單的示範。然而,它會令你的網頁變慢,而且並不適合發佈到線上環境。 -- React 官方手冊:快速嘗試 JSX 章節

之所以會令你的網頁變慢,是因為它是載入 babel.js 後在網頁執行時才編譯 JS 裡的 JSX 標籤內容,每次都要花時間編譯,網頁自然快不了。

原來網頁變慢是因為動態編譯 JSX,而不是 React 慢。

JSX 最佳用法就是預先編譯,但

官方的教學在此之後就是利用 NodeJS 把 babel 安裝到現有的 WebPack 專案中,再編譯 JSX 內容成為標準 JS 內容,最後才把編譯後的成品上傳到網站執行。

等等,這樣就不是逐步採用 React 了,要加入 WebPack 就等於是大改現有網站啦!

小結:要逐步採用 React 就不能使用 JSX

原有網站在沒有 WebPack 加持下,要逐步採用 React 勢必不能使用 JSX,因使用 Babel 動態轉換 JSX 的效率不佳。

結論就是:逐步採用 React 下又不要使用 NodeJS 編譯網站,React.createElement 你只能用它了。 😁

逐步採用 React 下的難題:外掛元件

逐步採用 React 雖然可以限縮自己要學習的內容,比如我只要學 ES6 + React.State + React.Props 等,只要會 CSS,要刻什麼都很簡單。

但外掛元件呢?

我的現有網站使用了大量的 EasyUI 和 DevExtreme 元件,這些元件僅提供 NodeJS 下載安裝,並沒有發佈編譯後可使用的 JS 檔案。

解決方案就只好在 React class 裡使用 jQuery 操作外掛元件?

 

這叫 React 入門到放棄。


所幸的是,在 DevExpress 官網裡有個叫【DevExtreme Tickets】的技術小本本,它記錄許多過往使用者所提出的技術問題和解決方案,其中一個主題是:Have demo about devextreme-react without npm?

DevExtreme 官方提出【提取+橋接】的概念。撇開現有網站,另開【Webpack-React-DevExtreme】專案,使用 WebPack 提取所需的元件,如 dxButton 等,再利用 NodeJS 編譯,把提取並編譯過的 JS 檔放入現有網站,如此就能在逐步採用 React 的場合下又能享受外掛元件的開發便利性。

而外掛元件則是存放到 DOM 的 window 裡,範例程式碼如下:

DevExtreme 提取 JS

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import * as React from "react";
import { Button } from "devextreme-react/button";

export class ButtonExample extends React.Component {
    // <Button icon="plus" text="Click me"/>
  constructor(props) {
    super(props);
  }
    render() {
        return (
          <Button {...this.props} />
        );
    }
}

const MyLib = {
    ButtonExample
}

export default MyLib

逐步採用 React 專案呼叫編譯後的 DevExtreme 元件

 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
<!doctype html>
<html>

<head>
  <meta charset="utf-8">
    <link rel='stylesheet' type='text/css' href='https://cdn3.devexpress.com/jslib/18.1.3/css/dx.common.css' />
    <link rel='stylesheet' type='text/css' href='https://cdn3.devexpress.com/jslib/18.1.3/css/dx.material.blue.light.css' />  
  <title>DevExtreme with webpack and React example</title>
</head>

<body>
  <div class="container">
    <h1>DevExtreme with webpack and React example</h1>
    <div id="app"></div>
    <div id="app2"></div>
  </div>
    <script src="https://unpkg.com/react@16/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
  <script src="js/app/bundle.js"></script>
  <script>
    const e = React.createElement
    ReactDOM.render(e(window.MyLib.ButtonExample, { icon:"plus",text:"Click me"}, null), document.getElementById("app2"))
  </script>
</body>

</html>

至於 Webpack-React-DevExtreme 原始碼,你可以在上述的票證裡找到。

結論時間

愛用 jQuery 是我個人的選擇,你也可以使用單純的 Vanilla JS,端看你的場合和上手程度來使用。

初學 React 最怕的就是在環境上的摸索上花很多時間,在不使用 npm、webpack 甚至是 Babel,現在我可以專注在學習 React 的設計上,並且和現有 HTML / JavaScript 專案進行互動,做中學所得到的回饋是最直接的。

官方不建議採用【逐步使用 React】?官方是建議使用 NPM Bundler,官方有說明建議原因,和相關資源也比較多,但有關於官方不建議【逐步使用 React】?我官方網站翻了翻,沒找到,有找到的期待你分享這類的相關資料。

React 裡要不要用 jQuery 是另一個問題,React 元件裡我是盡量用相容性比較高的 ES5/6 標準,至於 IE,我的環境不會遇到它,忽略也無仿。

在逐步採用 React 又要使用外掛元件,不再是魚和熊掌的問題,使用一個簡單 WebPack 專案 + 一次編譯,就能把所需的外掛元件放到 window 中,完全滿足「我全都要」的需求,實在是太棒啦!

如果有其它的外掛元件,我想也可以參照此方法把外掛元件產出。

底下是使用 Vanilla JS + React 使用 DevExtreme 小部件的範例。

和你分享 😉

SEE ALSO

2021/09/13

解析 Delphi 7 挑戰:從 WideString 到 UTF16 的轉換技術!

 

作者:吳祐賓

 


 

 

底下的程式內容到了 XE 後已經被 TEncoding 物件所取代,但這問題時常被問到,之前主流是 Base64,當時還可以用 EncdDecd 單元處理 Unicdoe 編碼,現在已逐漸改以 JSON 格式為主流,EncdDecd 單元已無法滿足眼下的需求了。

 

WideString 是 Delphi 7 相容 Unicode 的字串類型,然而 WideString 的存在是為了當時的 Win32 API 而存在的,它並不適合作為現代化的資料傳輸格式。

 

要達到良好的相容性,使用 UTF16 是比較好的選擇。可以確定的是 Delphi 7 沒有 WideString 對 JSON 的 Encode 和 Decode 的函式內容。不想自己寫?不囉嗦,直接為您獻上程式碼:

 

 

 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
function DecodeUTF16(sStr: string): WideString;
var
  i: Integer;
  StrPos: Integer;
  temp, top, last: string;
  ResultStr: WideString;
begin
  ResultStr := '';
  repeat
    StrPos := Pos('\u', sStr) - 1;
    if StrPos < 0 then begin            // 無 unicode 編碼不需轉換
      last := sStr;
      ResultStr := ResultStr + last;
      Break;
    end;
    top := Copy(sStr, 1, StrPos);       // 取出編碼字符前非 unicode 編碼的文字,如數字
    temp := Copy(sStr, StrPos + 1, 6);  // 取出編碼,包括 \u,如\u53f0
    Delete(temp, 1, 2);
    Delete(sStr, 1, StrPos + 6);
    ResultStr := ResultStr + top + WideChar(StrToInt('$' + temp));
  until (StrPos >= 0);
  Result := ResultStr;
end;

function EdenUTF16(const sStr: WideString): string;
var
  w: Word;
  StrPos: Integer;
  UTF16Str, TmpStr: string;
begin
  TmpStr := '';
  for StrPos:=1 to Length(sStr) do begin
    w := Ord(sStr[StrPos]);
    UTF16Str := IntToHex(w, 4);
    TmpStr := TmpStr +'\u'+ UTF16Str;
  end;
  Result := TmpStr;
end;

 

 

結論時間

 

以上的程式碼雖然能應用在實戰中,其 Delphi 7 裡的函式庫幾乎沒有對 WideString 處理的問題需要正視,在尋求古老三方元件的協助也無可避免需要手刻不足的區塊;自 Delphi XE 開始至現在的 Delphi 11 早已解決絕大部份 Unicode 的麻煩。

 

若你仍有中文處理需求,真心邀請你使用 Delphi CE 社群版來進行專案提升,省下你開發的時間,專注地在商業邏輯的開發會為你帶來更好的效益,畢竟公司開來目的是為了賺錢,而非程式碼研究,研究的事我來就行。😁

 

 

和你分享 😉

 

 

Delphi in Depth DataSnap 網站應用程式全端開發 出版