當我們在網路上使用應用程式時,像是購物網站、社交平台或是線上工具,背後其實有很多程式碼在幫忙處理畫面顯示和計算邏輯。其中,React 是一個非常受歡迎的工具,它能讓開發者更快、更方便地打造出漂亮、互動性強的網站。
那麼,為什麼我們不把這兩個厲害的工具結合起來呢?React 負責前端的畫面部分,Rust 負責背後的高效能運算,這樣一來,我們就能同時擁有美觀的介面和強大的運算能力。而這種結合的關鍵就是 WebAssembly,所以如果你需要處理一些複雜的計算,或是想要讓前端網頁執行得更快、更穩定,Rust 就派上用場了。
在這篇文章裡,我們會探討如何把 Rust 變成 WebAssembly之後,把它放進 React 應用程式中,一步步讓你親手打造一個簡單的網頁應用!
要將 Rust 與 React 結合,我們需要通過 WebAssembly 來將 Rust 程式碼轉換成可以在網頁上執行的格式。然後使用 JavaScript 來調用 Rust 中的函數,並將結果與 React 的 UI 結合。
在我們開始構建專案之前,讓我們先來了解一下什麼是 React。React 是由 Facebook 開發的一個前端 JavaScript 庫,最早於 2013 年推出。它的核心概念是組件化,也就是將整個使用者界面拆解成許多可以重複使用的小模組,這樣的設計讓開發者可以更輕鬆地維護和擴展應用程式。React 的其中一個大優勢是它使用了虛擬 DOM,這讓網頁在更新時只會改變實際需要變動的部分,從而提高了性能並讓網頁反應更快速。
此外,React 的生態系統非常豐富,擁有許多第三方工具和擴充套件,能夠幫助開發者更高效地完成各種需求。這也是為什麼 React 成為許多大型網站(例如 Facebook、Instagram)選擇的前端框架。
現在,既然我們對 React 有了初步的認識,接下來就讓我們開始建立開發環境。
要開始開發 React 應用程式,我們需要先準備好一些基礎工具。如果你是第一次使用 React,這裡有幾個步驟能幫助你快速上手。
安裝 Node.js:React 是建立在 JavaScript 之上,而 Node.js 能讓我們在本機執行 JavaScript 並且管理我們專案中的套件。如果你還沒有安裝 Node.js,可以前往 Node.js 官方網站 下載並安裝最新版本。安裝後,Node.js 會附帶一個叫做 npm
(Node Package Manager)的工具,這個工具用來安裝和管理 React 所需的套件。
使用 npm 或 yarn 管理套件:npm
是 Node.js 附帶的套件管理器,用來安裝和更新我們專案中的各種依賴。如果你更喜歡使用其他管理工具,也可以選擇 yarn
,這是一個相似的工具,兩者的功能和操作方式幾乎相同,取決於你的偏好。
有了這些工具之後,我們就可以開始建立 React 專案了。這裡我們會使用一個叫做 create-react-app
的工具來快速生成一個新的專案。create-react-app
是專門用來幫助開發者快速啟動一個 React 專案,並且自動配置好所有所需的環境的指令:
npx create-react-app rust-react-app
cd rust-react-app
這段指令會完成以下幾件事情:
npx create-react-app rust-react-app
:使用 npx
(npm 的工具)來執行 create-react-app
,並且自動建立一個名為 rust-react-app
的專案資料夾。cd rust-react-app
:進入剛剛建立的專案資料夾。此時,你會看到一個基本的 React 專案結構,裡面包含了所有你開發 React 應用所需的檔案和設定,讓你可以馬上開始撰寫程式。
當 create-react-app
成功建立專案後,專案目錄會長這樣:
rust-react-app/
├── node_modules/ # npm 下載的第三方依賴模組
├── public/ # 靜態資源目錄(HTML、圖像、字體等)
│ ├── index.html # React 應用程式的 HTML 主文件
│ └── favicon.ico # 網站的圖示
├── src/ # 源代碼目錄
│ ├── App.js # React 的主要應用程式文件
│ ├── App.css # 應用程式的樣式文件
│ ├── index.js # 應用的入口文件
│ └── index.css # 全局樣式
├── .gitignore # Git 版本控制忽略文件
├── package.json # npm 配置文件,列出所有依賴和腳本
└── README.md # 專案說明文件
這個目錄結構包含了我們開發 React 應用所需要的基本檔案,其中 src/
資料夾是我們主要會編寫代碼的地方,public/
資料夾則用來放靜態資源。
wasm-pack
現在,除了 React 之外,我們還需要準備一個工具來將 Rust 程式碼編譯成 WebAssembly。這個工具叫做 wasm-pack
,它是由 Rust 社群開發的,專門用來將 Rust 程式碼轉換成可以在瀏覽器中執行的 WebAssembly 格式。
要安裝 wasm-pack
,你只需要執行以下指令:
cargo install wasm-pack
這會透過 cargo
(Rust 的套件管理工具)來安裝 wasm-pack
,讓我們可以輕鬆將 Rust 程式碼轉換成 WebAssembly 格式,並將其整合到 React 應用中。
在 React 專案的根目錄下,我們會建立一個 Rust 專案,這個專案將用來編寫 WebAssembly 程式碼,並與 React 一起使用:
cargo new --lib rust_wasm
此時,專案目錄結構會變成如下所示,這個結構整合了 React 專案與 Rust 專案的檔案:
rust-react-app/
├── node_modules/ # npm 下載的第三方依賴模組
├── public/ # 靜態資源目錄(HTML、圖像、字體等)
│ ├── index.html # React 應用程式的 HTML 主文件
│ └── favicon.ico # 網站的圖示
├── src/ # React 的源代碼目錄
│ ├── App.js # React 的主要應用程式文件
│ ├── App.css # 應用程式的樣式文件
│ ├── index.js # 應用的入口文件
│ └── index.css # 全局樣式
├── rust_wasm/ # Rust 專案目錄,用於撰寫 WebAssembly 程式碼
│ ├── Cargo.toml # Rust 專案的配置檔案,列出專案的依賴和設定
│ └── src/
│ └── lib.rs # Rust 專案的主要程式碼檔案,撰寫 WebAssembly 相關邏輯
├── .gitignore # Git 版本控制忽略文件
├── package.json # npm 配置文件,列出所有依賴和腳本
└── README.md # 專案說明文件
在這個結構中,rust_wasm/
資料夾是我們用來編寫 Rust 程式碼的地方,其他檔案則是 React 專案的結構。這樣的目錄設置將幫助我們把 Rust 程式碼編譯成 WebAssembly,並與 React 應用程式結合。
Cargo.toml
在 Rust 專案的 Cargo.toml
檔案中,加入 WebAssembly 相關的依賴和設定:
[package]
name = "rust_wasm"
version = "0.1.0"
edition = "2021"
[dependencies]
wasm-bindgen = "0.2"
[lib]
crate-type = ["cdylib"]
wasm-bindgen
是一個讓 Rust 與 JavaScript 互操作的工具,它允許我們在 Rust 中定義函數,並將這些函數導出給 JavaScript 使用。
接下來,編寫我們的 Rust 程式碼。在 src/lib.rs
中,我們定義一個簡單的計算函數,這個函數將會被 React 調用:
use wasm_bindgen::prelude::*;
// 定義一個簡單的加法函數,並將其導出給 JavaScript 使用
#[wasm_bindgen]
pub fn add_numbers(a: i32, b: i32) -> i32 {
a + b
}
現在使用 wasm-pack
將 Rust 程式碼編譯為 WebAssembly:
cd rust_wasm
wasm-pack build --target web
這會生成一個 pkg
資料夾,裡面包含 .wasm
文件以及對應的 JavaScript 包裝代碼,這樣我們可以在 React 中調用這些函數。
現在回到 React 專案中,我們要將編譯好的 WebAssembly 整合到 React 應用中,其概念是要將pkg
資料夾內的檔案複製到 React 專案當中的 src
資料夾內,以便於前端可以直接引用。
cpy-cli
首先,我們需要安裝 cpy-cli
,這是一個能幫助我們在 npm install
後自動複製 WebAssembly 生成文件的工具。在專案目錄中,執行以下命令來安裝 cpy-cli
作為開發依賴:
npm install --save-dev cpy-cli
package.json
自動化複製 WebAssembly 文件接下來,我們將修改 package.json
中的 postinstall
腳本,以便在每次安裝依賴後,能夠自動將 rust_wasm/pkg
資料夾的內容複製到 src/rust_wasm_pkg
資料夾下。
根據不同的作業系統,路徑配置略有不同:
\\
來分隔/
來分隔您可以根據自己的作業系統選擇正確的配置。
package.json
設置:{
"scripts": {
"postinstall": "npx cpy .\\rust_wasm\\pkg\\* .\\src\\rust_wasm_pkg --parents"
}
}
package.json
設置:{
"scripts": {
"postinstall": "npx cpy './rust_wasm/pkg/*' './src/rust_wasm_pkg' --parents"
}
}
修改後的 package.json
應該會像這樣(以Windows系統為例)
{
"name": "rust-react-app",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"postinstall": "cpy .\\rust_wasm\\pkg\\* .\\src\\rust_wasm_pkg --parents"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"cpy-cli": "^5.0.0"
}
}
完成上述設定後,當我們執行 npm install
時,postinstall
腳本會自動執行並將 WebAssembly 編譯生成的 pkg
資料夾內容複製到 src/rust_wasm_pkg
。這樣 WebAssembly 文件可以被正確地載入到 React 專案中。
執行以下命令來進行檔案複製:
npm install
執行 npm install
之後,專案結構會變成:
rust-react-app/
├── node_modules/ # npm 下載的第三方依賴模組
├── public/ # 靜態資源目錄(HTML、圖像、字體等)
│ ├── index.html # React 應用程式的 HTML 主文件
│ └── favicon.ico # 網站的圖示
├── src/ # 源代碼目錄
│ ├── App.js # React 的主要應用程式文件
│ ├── App.css # 應用程式的樣式文件
│ ├── index.js # 應用的入口文件
│ ├── index.css # 全局樣式
│ └── rust_wasm_pkg/ # Rust 編譯的 WebAssembly 文件(從 rust_wasm/pkg 複製而來)
│ ├── rust_wasm_bg.wasm # 編譯生成的 WebAssembly 文件
│ ├── rust_wasm.js # 對應的 JavaScript 包裝文件
│ └── rust_wasm_bg.js # 背景 JavaScript 文件
├── rust_wasm/ # Rust 專案目錄,用於撰寫 WebAssembly 程式碼
│ ├── Cargo.toml # Rust 專案的配置檔案,列出專案的依賴和設定
│ ├── pkg/ # Rust 編譯生成的 WebAssembly 文件
│ │ ├── rust_wasm_bg.wasm # WebAssembly 二進位文件
│ │ ├── rust_wasm.js # 對應的 JavaScript 包裝文件
│ │ └── rust_wasm_bg.js # 背景 JavaScript 文件
│ └── src/
│ └── lib.rs # Rust 專案的主要程式碼檔案,撰寫 WebAssembly 相關邏輯
├── .gitignore # Git 版本控制忽略文件
├── package.json # npm 配置文件,列出所有依賴和腳本
└── README.md # 專案說明文件
從這個專案結構可以看出,我們在 rust_wasm
將 lib.rs
透過 wasm-pack
打包之後的 pkg
檔案直接複製到 rust-react-app/src
之下,以便於我們能夠直接引用這個 rust_wasm_pkg
套件。
接下來,我們可以在 React 應用中調用 WebAssembly 函數。打開 src/App.js
,並修改如下:
import React, { useState, useEffect } from 'react';
import init, { add_numbers } from './rust_wasm_pkg'; // 從 src 內部引用 WebAssembly 模組
function App() {
const [result, setResult] = useState(null);
useEffect(() => {
const initWasm = async () => {
await init(); // 初始化 WebAssembly 模組
const sum = add_numbers(10, 20); // 調用 Rust 函數進行計算
setResult(sum);
};
initWasm();
}, []);
return (
<div className="App">
<header className="App-header">
<h1>Rust 與 React 整合</h1>
<p>計算 10 + 20 的結果:{result !== null ? result : '計算中...'}</p>
</header>
</div>
);
}
export default App;
這段 App.js
的代碼是用來將 WebAssembly 模組整合到 React 中,並通過 React 調用 WebAssembly 函數,顯示計算結果。 React 的基本概念是透過 js
檔案建立 組件
,在 App.js
當中我們會建立一個名為 App
的網頁組件,其中因為 App.js
本身支援 Javscript 的語法,並且能夠以 JSX
的格式將 HTML 結構被應用在這個組件,當我們完成這個組件的之後,就會將該組件輸出,以便於 public
裡面的 index.html
可以引用 App
這個組件,那就會在網頁上呈現我們於 App.js
編輯的網頁內容了,那麼接下來我們進入詳細說明:
import React, { useState, useEffect } from 'react';
import init, { add_numbers } from './rust_wasm_pkg'; // 從 src 內部引用 WebAssembly 模組
import
語句:這一行從 React 庫中引入兩個 Hook:useState
和 useEffect
。React Hook 是用來處理狀態和生命周期的工具,因為 Hook 有太多種了,這邊簡單介紹。useState
的用途就是建立一個[變數, 改變此變數的函數]
的組合,讓組件內可以把變數用到需要使用的位置,並且在需要的時候的時候透過後者去改變變數的值,常見的使用模板是 const [val, setVal] = useState(null);
, 其中 val
跟 setVal
是自定義的變數名稱與函數名稱,常用寫法就是駝峰式的命名法,而 =useState();
就是將其定義為 useState
Hook,而 ()
所放入的是這個變數被預設的初始值。useEffect
可以簡單理解成是當 App.js
文件被讀取完畢之後,接著要直接執行的程式。後面可以看到實際應用範例。import init, { add_numbers } from './rust_wasm_pkg';
:這行代碼是從剛剛編譯並複製到 src/rust_wasm_pkg
的 WebAssembly 模組中引入 init
和 add_numbers
函數。init
是用來初始化 WebAssembly 模組的函數,而 add_numbers
是 Rust 中定義的加法函數。function App() {
const [result, setResult] = useState(null);
function App()
:這是定義 React 組件的方式。React 組件是一個 JavaScript 函數,負責返回要顯示的 UI(即 HTML 結構)。useState(null)
:useState
是 React 提供的 Hook,用來管理組件的狀態。這裡我們定義了一個名為 result
的狀態變數,初始值為 null
。setResult
是更新 result
的函數,當我們想要改變 result
的值時,就會調用它。useEffect(() => {
const initWasm = async () => {
await init(); // 初始化 WebAssembly 模組
const sum = add_numbers(10, 20); // 調用 Rust 函數進行計算
setResult(sum); // 更新結果狀態
};
initWasm();
}, []);
useEffect()
:這是另一個 React Hook,用來處理副作用操作。簡單來說,useEffect
可以讓你在組件加載時或更新時執行特定的代碼邏輯。在這裡,我們使用 useEffect
在組件加載時初始化 WebAssembly 模組並調用它的函數。const initWasm = async () => {}
:這個定義一個異步函數,因為 WebAssembly 的初始化和函數調用可能需要一點時間,所以我們使用 async/await
來確保這些操作完成後再進行下一步。await init()
:這一行調用我們從 WebAssembly 模組中引入的 init
函數,初始化 WebAssembly 模組,使得之後可以正常調用它的函數。const sum = add_numbers(10, 20)
:這是從 WebAssembly 中調用 Rust 定義的 add_numbers
函數,將兩個數字相加。這裡我們傳遞的參數是 10 和 20。setResult(sum)
:將計算結果儲存到 result
狀態變數中,這樣可以觸發 React 重新渲染組件,顯示計算結果。return (
<div className="App">
<header className="App-header">
<h1>Rust 與 React 整合</h1>
<p>計算 10 + 20 的結果:{result !== null ? result : '計算中...'}</p>
</header>
</div>
);
return
語句:這一部分是 React 組件的返回值,用來定義網頁上要顯示的 UI 結構。這裡我們返回了一個 div
,裡面包含一個 header
和一個 p
標籤。{result !== null ? result : '計算中...'}
:這是一個條件表達式,根據 result
的值來決定顯示的內容。如果 result
不為 null
,則顯示計算結果;如果 result
仍然為 null
(表明計算還在進行中),則顯示 "計算中..."。export default App;
export default
:這是 ES6 語法,用來導出這個 App
組件,以便在其他地方引用。useEffect
會觸發 initWasm
函數,這個函數會初始化 WebAssembly 模組,並調用 add_numbers(10, 20)
進行計算。result
狀態變數中,並觸發 React 重新渲染頁面。這段代碼展示了如何在 React 應用中調用 WebAssembly 函數,並將計算結果顯示在頁面上。即使沒有使用過 React,也可以按照這個步驟輕鬆理解和實現。
現在,我們可以運行 React 應用來查看效果:
npm start
這將啟動本地伺服器,並在瀏覽器中打開應用。你應該可以看到頁面顯示「計算 10 + 20 的結果:30」。
這樣就完成了 Rust 與 React 的整合,當然我們可以用的並不僅限於簡單的數學計算。Rust 可以用來處理更複雜的邏輯,這樣的整合可以充分發揮 Rust 的高效能計算優勢,同時利用 React 強大的前端框架來處理用戶界面和互動。
如果後續我們更新了 Rust 的函數,也可以透過以下步驟完成更新 pkg
內容,並且複製到 react-app/src
當中
cd rust_wasm
wasm-pack build --target web
cd ..
npm install
接下來你可以依據需要,直接開啟 React 網頁
npm install
或者用 webpack 打包成模板
npm run build
打包後的檔案會被建立在 build
資料夾內,其中的 index.html
即為打包後的 React 網頁,透過這樣的步驟就可以不斷迭代前端網頁的版本了。
在這篇文章中,我們展示了如何將 Rust 編譯為 WebAssembly,並將其與 React 結合,構建一個簡單的 Web 應用。Rust 的高效能和內存安全特性讓它成為處理高計算負荷任務的理想選擇,目前這種透過 WebAssembly 將 Rust 打包成瀏覽器可讀取的,還是屬於前端語言的範疇,主要是方便讓我們透過像是 React 這樣的前端框架可以引入結合用。
而我們將 React 前端網頁,透過 npm run build
打包之後,就可以與各種後端語言的專案結合了,包括 node.js, python, java, php, go 或者是 rust,如果是使用 python ,常用的如 flask
或者 Django
也都是適用的,所以接著下來,我們也會介紹到如何將 rust 作為後端語言,透過 Actix
框架建立完整的前後端應用,不過首先,我們還會對 rust 本身有更多的認識。