扣丁书屋

来了来了。。。使用 Rust 编写更快的 React 组件

大家好,上周发了一篇 Wasm 的文章,主要分析的是今年 Google 开发者大会上的 Wasm 主题:

[Wasm 为 Web 开发带来无限可能]

其实主要还是我个人对 Rust 比较感兴趣,在今天的文章中,我将带大家完成一个将 Rust 实际应用到 React 项目中的小 Demo

Wasm

在开始之前,我们还是先来回顾下 Wasm:

WebAssembly 是一种二进制指令格式,简称为 Wasm,它可以运行在适用于堆栈的虚拟机上。

WebAssembly 存在的意义就是成为编程语言的可移植编译目标,让在 Web 上部署客户端和服务端应用成为可能。

Wasm 具有紧凑的二进制格式,可为我们提供近乎原生的网络性能。随着它变得越来越流行,许多语言都编写了编译成 Web 程序集的绑定工具。

为什么是 Rust

Rust 是一个快速、可靠二期又节约内存的编程语言。在过去六年的 stackoverflow 的最受喜爱的编程语言中,它一直蝉联榜首的位置,主要还是这个语言本身拥有众多的优点,比如:

  • 内存安全
  • 类型安全
  • 消除数据竞争
  • 使用前编译
  • 建立(并且鼓励)在零抽象之上
  • 最小的运行时(无停止世界的垃圾搜集器,无 JIT 编译器,无 VM
  • 低内存占用(程序可以运行在资源受限的环境,比如小的微控制器)
  • 针对裸机(比如,写一个 OS 内核或者设备驱动,把 Rust 当一个 ‘高层’汇编器使用)”

另外,RustWebAssembly 领域的贡献非常大的,使用 Rust 编写 WebAssembly 非常简单。

但是,Rust 存在的目的不是为了替代 JavaScript 而是和他形成互补,因为 Rust 语言的学习曲线是非常陡峭的,用它去完全替代 Web 开发几乎是不可能的。

所以,我们一般会在 Web 开发的工具链,或者前端页面中一些非常大量的数据计算中的操作用到它。

前置知识

在开始开发之前,你需要了解一些前置知识,React 相关的就不多说了,我们来看看 Rust 相关的几个重要概念。

cargo

cargorust 的代码组织和包管理工具,你可以将它类比为 node.js 中的 npm

cargo 提供了一系列强大的功能,从项目的建立、构建到测试、运行直至部署,为 rust 项目的管理提供尽可能完整的手段。同时,它也与 rust 语言及其编译器 rustc 本身的各种特性紧密结合。

rustup

rustupRust 的安装和工具链管理工具,并且官网推荐使用 rustup 安装 Rust

rustuprustc(rust编译器) 和 cargo 等工具安装在 Cargobin 目录,但这些工具只是 Rust 工具链中组件的代理,真正工作的是工具链中的组件。通过 rustup 的命令可以指定使用不同版本的工具链。

wasm-bindgen

wasm-bindgen 提供了 JSRust 类型之间的桥梁,它允许 JS 使用字符串调用 Rust API,或者使用 Rust 函数来捕获 JS 异常。

wasm-bindgen 的核心是促进 javascriptRust 之间使用 wasm 进行通信。它允许开发者直接使用 Rust 的结构体、javascript的类、字符串等类型,而不仅仅是 wasm 支持的整数或浮点数类型。

wasm-pack

wasm-packRust / Wasm 工作组开发维护,是现在最为活跃的 WebAssembly 应用开发工具。

wasm-pack 支持将代码打包成 npm 模块,并且附带 Webpack 插件(wasm-pack-plugin),借助它,我们可以轻松的将 Rust 与已有的 JavaScript 应用结合。

wasm32-unknown-unknown

通过 rustuptarget 命令可以指定编译的目标平台,也就是编译后的程序在哪种操作系统上运行。

wasm-pack 使用 wasm32-unknown-unknown 目标编译代码。

好了,了解了 Rust 相关的一些知识,我们一起来完成这个 Demo 吧。

一起来做个 Demo

在开始之前,要确保你的电脑上已经安装了 NodeRust,可以在命令行分别输入 npmrustup 看看能否找到命令,如果没安装的话自己先安装一下。

初始化一个简单 React 程序

首先,我们来初始化一个 React 项目,命令行执行 npm init

然后,我们安装一些开发项目必备的包:

$ 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

然后,我们在项目中创建一些常用的文件夹:srcpagepublicbuild、和 dist

我们在 page 文件夹中创建一个 index.jsx,编写一些测试代码:

import React from 'react';
import ReactDOM from 'react-dom';

ReactDOM.render(<h1>code秘密花园 Hello, world!</h1>, document.getElementById('root'));

然后,我们为 babelwebpack 创建两个配置文件:

.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 下创建一个 index.html

<!DOCTYPE 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"
  }
}

下面,执行 npm install,然后 npm run dev,你就可以跑起来一个非常简单的 React 应用:

引入 Rust

好了,下面我们来编写我们的 Rust 组件(别忘了回顾下上面提到的 Rust 前置知识),首先我们使用 Rust 的包管理工具 cargo 来初始化一个简单的 Rust 应用程序:

cargo init --lib .

执行完之后,会创建一个 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"

现在,你可以先尝试执行下 cargo build

第一次执行可能会比较慢,可以 Google 搜一下怎么将 cargo 配置为国内源。

好了,上面只是测试一下构建,它现在还派不上用场,我们下面还要执行一下编译目标,执行:

$ rustup target add wasm32-unknown-unknown

指定好 wasm32-unknown-unknown 这个编译目标,我们才能把它应用到我们的 React 程序中,下面我们给我们的 src/lib.rs 写两个简单的函数:

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 这个命令行工具,以便我们能利用我们创建的 WebAssembly 代码:

$ cargo install -f wasm-bindgen-cli

安装后,我们可以使用 Rust 生成的 WebAssembly 给我们的 React 代码创建一个包:

$ wasm-bindgen target/wasm32-unknown-unknown/debug/react_wasm.wasm --out-dir build

执行完成后,编译好的 JavaScript 包和优化好的 Wasm 代码会保存到我们的 build 目录中,以供 React 程序使用。

在 React 程序中应用 Wasm

下面,我们尝试一下在我们的 React 程序中用上这些 Wasm 代码,我们现在 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",

然后我们执行 npm run build 就可以打包所有代码啦。

下面,我们还需要安装一下上面我们提到的 wasm-packWebpack 插件,它可以帮助我们把 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
  }

下面,执行一下这几个命令:npm run build:wasm、npm run build:bindgen、npm run build,应该都不会报错。

最后,我们在我们的 React 组件中调用一下我们刚刚生成的 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 there</h1>
          <button onClick={m.big_computation}>Run Computation</button>
        </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/

https://mp.weixin.qq.com/s/ATOySeIPWJCf9dmoxMQUIw

深入 React 函数组件的 re-render 原理及优化

发布于:1年以前  |  615次阅读  |  详细内容 »

手写系列-实现一个铂金段位的 React

发布于:1年以前  |  484次阅读  |  详细内容 »

如何写出更优雅的 React 组件 - 代码结构篇

发布于:1年以前  |  581次阅读  |  详细内容 »

React 精品组件:mac-scrollbar

发布于:1年以前  |  548次阅读  |  详细内容 »

React 精品组件:mac-scrollbar

发布于:1年以前  |  530次阅读  |  详细内容 »

React 18 Beta 来了

发布于:1年以前  |  630次阅读  |  详细内容 »

手把手教你写一个 React 状态管理库

发布于:1年以前  |  568次阅读  |  详细内容 »

如何将 React 应用程序加载时间减少 70%

发布于:1年以前  |  618次阅读  |  详细内容 »

React 18 新特性之 startTransition

发布于:1年以前  |  586次阅读  |  详细内容 »

Reaction Engines成立新合资公司以打造紧凑、轻质的氨反应器

英国的Reaction Engines公司已经宣布成立一家合资企业以打造紧凑、轻质的氨反应器。这家公司表示,这种反应器可以用于航运和离网能源发电等困难行业的脱碳,并且还可以用于航空。

发布于:1年以前  |  639次阅读  |  详细内容 »

你不知道的 React 最佳实践

发布于:1年以前  |  515次阅读  |  详细内容 »

这才是 React Hooks 性能优化的正确姿势

发布于:1年以前  |  562次阅读  |  详细内容 »

所属标签

最多阅读

Android插件化方案 3年以前  |  235045次阅读
SourceTree,让你忘掉Git命令的工具 3年以前  |  3705次阅读
朴素贝叶斯分类器的应用 3年以前  |  3356次阅读
在Python的Django框架中包装视图函数 3年以前  |  3247次阅读
用Sublime打造Protobuf迷你IDE 3年以前  |  3241次阅读
Python贪吃蛇游戏编写代码 3年以前  |  3224次阅读
在Sublime中高亮显示Proto Buffer 3年以前  |  3222次阅读
Google Shell 风格指南 3年以前  |  3110次阅读
Google Python 风格指南 3年以前  |  3097次阅读
Genymotion下载及安装使用 3年以前  |  3045次阅读
Markdown语法 转义 3年以前  |  2990次阅读
Markdown语法 链接 3年以前  |  2860次阅读
Markdown语法 段落内代码 3年以前  |  2814次阅读
java中NIO与传统IO 3年以前  |  2769次阅读

手机扫码阅读