使用 Rust 編寫更快的 React 組件
大家好,我是 ConardLi,上周發(fā)了一篇 Wasm 的文章,主要分析的是今年 Google 開發(fā)者大會上的 Wasm 主題:
其實主要還是我個人對 Rust 比較感興趣,在今天的文章中,我將帶大家完成一個將 Rust 實際應用到 React 項目中的小 Demo。
Wasm
在開始之前,我們還是先來回顧下 Wasm:

WebAssembly 是一種二進制指令格式,簡稱為 Wasm,它可以運行在適用于堆棧的虛擬機上。
WebAssembly 存在的意義就是成為編程語言的可移植編譯目標,讓在 Web 上部署客戶端和服務端應用成為可能。

Wasm 具有緊湊的二進制格式,可為我們提供近乎原生的網(wǎng)絡性能。隨著它變得越來越流行,許多語言都編寫了編譯成 Web 程序集的綁定工具。
為什么是 Rust
Rust 是一個快速、可靠二期又節(jié)約內(nèi)存的編程語言。在過去六年的 stackoverflow 的最受喜愛的編程語言中,它一直蟬聯(lián)榜首的位置,主要還是這個語言本身擁有眾多的優(yōu)點,比如:
內(nèi)存安全 類型安全 消除數(shù)據(jù)競爭 使用前編譯 建立(并且鼓勵)在零抽象之上 最小的運行時(無停止世界的垃圾搜集器,無 JIT編譯器,無VM)低內(nèi)存占用(程序可以運行在資源受限的環(huán)境,比如小的微控制器) 針對裸機(比如,寫一個 OS內(nèi)核或者設備驅(qū)動,把 Rust 當一個 ‘高層’匯編器使用)”
另外,Rust 在 WebAssembly 領域的貢獻非常大的,使用 Rust 編寫 WebAssembly 非常簡單。
但是,Rust 存在的目的不是為了替代 JavaScript 而是和他形成互補,因為 Rust 語言的學習曲線是非常陡峭的,用它去完全替代 Web 開發(fā)幾乎是不可能的。
所以,我們一般會在 Web 開發(fā)的工具鏈,或者前端頁面中一些非常大量的數(shù)據(jù)計算中的操作用到它。
前置知識
在開始開發(fā)之前,你需要了解一些前置知識,React 相關的就不多說了,我們來看看 Rust 相關的幾個重要概念。
cargo

cargo 是 rust 的代碼組織和包管理工具,你可以將它類比為 node.js 中的 npm。
cargo 提供了一系列強大的功能,從項目的建立、構建到測試、運行直至部署,為 rust 項目的管理提供盡可能完整的手段。同時,它也與 rust 語言及其編譯器 rustc 本身的各種特性緊密結合。
rustup
rustup 是 Rust 的安裝和工具鏈管理工具,并且官網(wǎng)推薦使用 rustup 安裝 Rust。

rustup 將 rustc(rust編譯器) 和 cargo 等工具安裝在 Cargo 的 bin 目錄,但這些工具只是 Rust 工具鏈中組件的代理,真正工作的是工具鏈中的組件。通過 rustup 的命令可以指定使用不同版本的工具鏈。
wasm-bindgen

wasm-bindgen 提供了 JS 和 Rust 類型之間的橋梁,它允許 JS 使用字符串調(diào)用 Rust API,或者使用 Rust 函數(shù)來捕獲 JS 異常。
wasm-bindgen 的核心是促進 javascript 和 Rust 之間使用 wasm 進行通信。它允許開發(fā)者直接使用 Rust 的結構體、javascript的類、字符串等類型,而不僅僅是 wasm 支持的整數(shù)或浮點數(shù)類型。
wasm-pack

wasm-pack 由 Rust / Wasm 工作組開發(fā)維護,是現(xiàn)在最為活躍的 WebAssembly 應用開發(fā)工具。
wasm-pack 支持將代碼打包成 npm 模塊,并且附帶 Webpack 插件(wasm-pack-plugin),借助它,我們可以輕松的將 Rust 與已有的 JavaScript 應用結合。
wasm32-unknown-unknown
通過 rustup 的 target 命令可以指定編譯的目標平臺,也就是編譯后的程序在哪種操作系統(tǒng)上運行。
wasm-pack 使用 wasm32-unknown-unknown 目標編譯代碼。
好了,了解了 Rust 相關的一些知識,我們一起來完成這個 Demo 吧。
一起來做個 Demo
在開始之前,要確保你的電腦上已經(jīng)安裝了 Node 和 Rust,可以在命令行分別輸入 npm、rustup 看看能否找到命令,如果沒安裝的話自己先安裝一下。
初始化一個簡單 React 程序
首先,我們來初始化一個 React 項目,命令行執(zhí)行 npm init:

然后,我們安裝一些開發(fā)項目必備的包:
$?npm?i?react?react-dom
$?npm?i?-D?webpack?webpack-cli?webpack-dev-server?html-webpack-plugin?
$?npm?i?-D?babel-core?babel-loader?@babel/preset-env?@babel/preset-react
然后,我們在項目中創(chuàng)建一些常用的文件夾:src、page、public、build、和 dist。
我們在 page 文件夾中創(chuàng)建一個 index.jsx,編寫一些測試代碼:
import?React?from?'react';
import?ReactDOM?from?'react-dom';
ReactDOM.render(<h1>code秘密花園?Hello,?world!h1>,?document.getElementById('root'));
然后,我們?yōu)?babel 和 webpack 創(chuàng)建兩個配置文件:
.babelrc:
{
????"presets":?[
????????"@babel/preset-env",
????????"@babel/preset-react"
????]
}
webpack.config.js:
const?HtmlWebpackPlugin?=?require('html-webpack-plugin');
const?path?=?require('path');
module.exports?=?{
??entry:?'./page/index.jsx',
??output:?{
????path:?path.resolve(__dirname,?'dist'),
????filename:?'bundle.[hash].js',
??},
??devServer:?{
????compress:?true,
????port:?8080,
????hot:?true,
????static:?'./dist',
????historyApiFallback:?true,
????open:?true,
??},
??module:?{
????rules:?[
??????{
????????test:?/.(js|jsx)$/,
????????exclude:?/node_modules/,
????????use:?{
??????????loader:?'babel-loader',
????????},
??????},
????],
??},
??plugins:?[
????new?HtmlWebpackPlugin({
??????template:?`${__dirname?}/public/index.html`,
??????filename:?'index.html',
????}),
??],
??mode:?'development',
??devtool:?'inline-source-map',
};
然后,在 public 下創(chuàng)建一個 index.html:
html>
<html?lang="en">
<head>
????<meta?charset="UTF-8">
????<meta?name="viewport"?content="width=device-width,?initial-scale=1.0">
????<title>code秘密花園title>
head>
<body>
????<div?id="root">div>
body>
html>
下面檢查下你的 package.json,看看和我的是不是一樣:
{
??"name":?"react-wasm",
??"version":?"1.0.0",
??"description":?"一個?Rust?編寫?React?組件的?Demo",
??"main":?"src/index.jsx",
??"scripts":?{
????"dev":?"webpack?server"
??},
??"keywords":?[],
??"author":?"ConardLi",
??"license":?"MIT",
??"dependencies":?{
????"react":?"^17.0.2",
????"react-dom":?"^17.0.2"
??},
??"devDependencies":?{
????"@babel/core":?"^7.16.0",
????"@babel/preset-env":?"^7.16.4",
????"@babel/preset-react":?"^7.16.0",
????"babel-loader":?"^8.2.3",
????"html-webpack-plugin":?"^5.5.0",
????"webpack":?"^5.64.2",
????"webpack-cli":?"^4.9.1",
????"webpack-dev-server":?"^4.5.0"
??}
}
下面,執(zhí)行 npm install,然后 npm run dev,你就可以跑起來一個非常簡單的 React 應用:

引入 Rust
好了,下面我們來編寫我們的 Rust 組件(別忘了回顧下上面提到的 Rust 前置知識),首先我們使用 Rust 的包管理工具 cargo 來初始化一個簡單的 Rust 應用程序:
cargo?init?--lib?.
執(zhí)行完之后,會創(chuàng)建一個 Cargo.toml 和一個 src/lib.rc 文件。
然后,我們在 ?Cargo.toml 中引入 wasm-bindgen 這個包,另外我們還需要告訴編譯器這個包是一個 cdylib:
[package]
name = "react-wasm"
version = "1.0.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2"
現(xiàn)在,你可以先嘗試執(zhí)行下 cargo build:

第一次執(zhí)行可能會比較慢,可以
cargo配置為國內(nèi)源。
好了,上面只是測試一下構建,它現(xiàn)在還派不上用場,我們下面還要執(zhí)行一下編譯目標,執(zhí)行:
$?rustup?target?add?wasm32-unknown-unknown
指定好 wasm32-unknown-unknown 這個編譯目標,我們才能把它應用到我們的 React 程序中,下面我們給我們的 src/lib.rs 寫兩個簡單的函數(shù):
use?wasm_bindgen::prelude::*;
#[wasm_bindgen]
extern?"C"?{
????fn?alert(s:?&str);
}
#[wasm_bindgen]
pub?fn?big_computation()?{
????alert("這個是一個超級耗時的復雜計算邏輯");
}
#[wasm_bindgen]
pub?fn?welcome(name:?&str)?{
???alert(&format!("Hi 我是?{}?,我在 code秘密花園?!",?name));
}
為了確保我們的 Rust 應用程序正常工作,我們重新用 wasm32-unknown-unknown 編譯一下:
$?cargo?build?--target?wasm32-unknown-unknown
然后我們安裝一下 wasm-bindgen-cli 這個命令行工具,以便我們能利用我們創(chuàng)建的 WebAssembly 代碼:
$?cargo?install?-f?wasm-bindgen-cli
安裝后,我們可以使用 Rust 生成的 WebAssembly 給我們的 React 代碼創(chuàng)建一個包:
$?wasm-bindgen?target/wasm32-unknown-unknown/debug/react_wasm.wasm?--out-dir?build
執(zhí)行完成后,編譯好的 JavaScript 包和優(yōu)化好的 Wasm 代碼會保存到我們的 build 目錄中,以供 React 程序使用。
在 React 程序中應用 Wasm
下面,我們嘗試一下在我們的 React 程序中用上這些 Wasm 代碼,我們現(xiàn)在 package.json 中添加一些常用的 npm 腳本:
??"build:wasm":?"cargo?build?--target?wasm32-unknown-unknown",
??"build:bindgen":?"wasm-bindgen?target/wasm32-unknown-unknown/debug/rusty_react.wasm?--out-dir?build",
??"build":?"npm?run?build:wasm?&&?npm?run?build:bindgen?&&?npx?webpack",
然后我們執(zhí)行 npm run build 就可以打包所有代碼啦。
下面,我們還需要安裝一下上面我們提到的 wasm-pack 的 Webpack 插件,它可以幫助我們把 Wasm 代碼打包成 NPM 模塊:
npm?i?-D?@wasm-tool/wasm-pack-plugin
最后更新一下我們的 webpack.config.js,添加下面的配置:
const?WasmPackPlugin?=?require("@wasm-tool/wasm-pack-plugin");
??...
??plugins:?[
????...
????new?WasmPackPlugin({
??????crateDirectory:?path.resolve(__dirname,?".")
????}),
??],
??...
??experiments:?{
????asyncWebAssembly:?true
??}
下面,執(zhí)行一下這幾個命令:npm run build:wasm、npm run build:bindgen、npm run build,應該都不會報錯。

最后,我們在我們的 React 組件中調(diào)用一下我們剛剛生成的 Wasm 模塊:
import?React,?{?useState?}?from?"react";
import?ReactDOM?from?"react-dom";
const?wasm?=?import("../build/rusty_react");
wasm.then(m?=>?{
??const?App?=?()?=>?{
????const?[name,?setName]?=?useState("");
????const?handleChange?=?(e)?=>?{
??????setName(e.target.value);
????}
????const?handleClick?=?()?=>?{
??????m.welcome(name);
????}
????return?(
??????<>
????????<div>
??????????<h1>Hi?thereh1>
??????????<button?onClick={m.big_computation}>Run?Computationbutton>
????????div>
????????<div>
??????????<input?type="text"?onChange={handleChange}?/>
??????????<button?onClick={handleClick}>Say?hello!button>
????????div>
??????>
????);
??};
??ReactDOM.render(<App?/>,?document.getElementById("root"));
});
下面,你就可以在 React 組件中愉快的使用 Rust 了!

參考
https://www.rust-lang.org/learn https://rustwasm.github.io/ https://www.joshfinnie.com/blog/using-webassembly-created-in-rust-for-fast-react-components/
最后
遷移公眾號后之前的星標就丟失啦,而且之前的一些長讀記錄也會丟失,所以你可能會經(jīng)常收不到我公眾號的消息推送,大家可以點公眾號右上角的更多(...)— 設為星標,
我的書 「《算法通關之路》」 已經(jīng)出版了,想要突破算法面試的朋友不要錯過,京東淘寶當當亞馬遜等均有出售,電子版也有哦~
后臺回復:typescript,獲取我寫的 typescript 系列文章,絕對精品 后臺回復:電子書,自動獲取我為大家整理的大量經(jīng)典電子書,省去你篩選以及下載的時間 后臺回復:不一樣的前端,自動獲取精選優(yōu)質(zhì)前端文章。 后臺回復:算法,自動獲取精選算法文章。另外也可關注我的另外一個專注算法的公眾號力扣加加。 后臺回復:每日一薦,自動獲取我為大家總結的每日一薦月刊,內(nèi)含精品文章,實用技巧,高效工具等等

