前言
在本文中,我們將使用 Golang WebAssembly 創建一個簡單的 TODO 應用,完全使用 Golang 來操作 DOM,整個代碼的複雜度不高,但足以用來學習 Golang 撰寫 WebAssembly 的基本技巧。
成果
環境
- go version go1.19.3 linux/amd64
讓開發順利的前置作業
若是使用 Visual Studio Code 開發 WebAssembly,大概率會遇到 could not import syscall/js 的提示,這是因為 syscall/js 是在 wasm 架構上才能引用的,而 VSCode 預設不會將 wasm 視為編譯的目標架構 (GOARCH),這時候需要在 .vscode 目錄中創建如下內容的 settings.json,讓 VSCode 知道我們的目標架構是 wasm
1 | { |
TODO 應用原始碼說明
Source Code On GitHub 提供完整的程式碼可以參考,原始碼中有非常詳細的註解,請讀者直接閱讀註解來學習,這裡僅說明 syscall/js 相關函數的使用方法。
js.Value
對 DOM 操作取得的資料 (包含 Element, 資料等等) 為 js.Value 型態。例如:js.Global() 為取得 window 物件 (在 Browser 環境) 或 global 物件 (在 Node 環境) 的 Function,回傳的型態為 js.Value。
js.Value.Get()
js.Value.Get() 可以取得該物件的屬性 (Property)。
例如:js.Global().Get("document") 就是取得 window 物件的 document Property,即等同 JavaScript 的 window.document。
1 | document = js.Global().Get("document") |
js.Value.Call()
js.Value.Call() 可以呼叫該物件的方法 (Method),並提供輸入參數。
例如:document.Call("getElementById", "root") 就是呼叫 document 物件的 getElementById Method 並將 "root" 作為該 Method 的輸入參數,即等同 JavaScript 的 document.getElementById("root")。
1 | root = document.Call("getElementById", "root") |
js.Value.Set()
js.Value.Set() 可以設定該物件的 Property,並提供要設定的值。
例如:header.Set("textContent", "Golang Web Assembly for TODO :)") 就是設定該 Element 的 Property textContent 為 "Golang Web Assembly for TODO :)",即等同 JavaScript 的 header.textContent = "Golang Web Assembly for TODO :)"。
1 | header.Set("textContent", "Golang Web Assembly for TODO :)") |
js.Value.Bool()
當確定 js.Value 為 Bool 型態時,可以透過 js.Value.Bool() 將其轉換為 Bool。
例如:event.Get("currentTarget").Get("checked").Bool() 就是將 event.currentTarget.checked 的值轉為 Bool
1 | todo.Completed = event.Get("currentTarget").Get("checked").Bool() |
當然還有像 js.Value.String(), js.Value.Int() 等等取值的 Methods 可以使用,這裡就不一一列舉。要注意的一點是若轉換失敗會 panic,例如:該 js.Value 不是 Bool,卻使用 Bool() Method 取值則會 panic。
js.FuncOf
當需要將 Golang 的 Function 暴露給 Browser 使用時,必須使用 js.FuncOf 將符合以下簽名形式的 Function 包裝起來
1 | func(this js.Value, args []js.Value) any |
this: 能透過此參數使用 JavaScript 中的this物件。args: 呼叫該 Function 時所帶的輸入參數。- 回傳值:從 Golang 回傳給 Browser 的值。
在原始碼中會看到每個 addEventListener 都是使用這樣的 Function,例如
1 | checkBox.Call("addEventListener", "change", js.FuncOf(func(this js.Value, args []js.Value) any { |
編譯
指定 GOARCH=wasm 與 GOOS=js,同時將編譯出的 wasm 檔案存到 ./build/ 目錄下且命名為 main.wasm
1 | GOARCH=wasm GOOS=js go build -o ./build/main.wasm |
index.html
導入 main.wasm 到 html 使用前,還有一件重要的事要先做,也就是需要先引用 wasm_exec.js (它的位置在 $(go env GOROOT)/misc/wasm/wasm_exec.js,可以將它複製到目標目錄下),我們才可以執行 new Go(),再透過 WebAssembly.instantiateStreaming 取得 main.wasm 並編譯成 Browser 可以執行的代碼,最後再使用 go.run() 運行就大功告成
1 | <head> |
進一步練習的方向
(難度 ★☆☆) 讓 Task 的順序可以被調整。
(難度 ★★☆) 將資料儲存在 Browser,讓資料在頁面重新整理後不會消失。
(難度 ★★★) 目前 Re-render 機制是當每修改一個資料就要執行一次整個清單的 Render,這樣的機制會造成效率低下,讀者可以思考如何只重新 Render 需要 Render 的地方。
結語
在這篇文章中,我們學習了如何使用 Golang WebAssembly 編寫一個 TODO 應用,然後將其編譯成 (*.wasm),並將 *.wasm 引入到 HTML 頁面中。最後,我們成功地創建了一個可運行在瀏覽器中的 TODO 應用程序。
使用 WebAssembly,可以編寫高效的應用程序,並將其部署到 Web 平臺上。由於 WebAssembly 能夠與現有 Web 技術相互操作,因此它在實現高效計算、加速圖形和數據處理等方面具有潛力。希望這篇文章能夠幫助讀者更好地理解如何使用 Golang WebAssembly 編寫 Web 應用程序。