Rust跨平台JS解释器库dioxus-interpreter-js的使用,实现Dioxus框架与JavaScript的高效交互
Rust跨平台JS解释器库dioxus-interpreter-js的使用,实现Dioxus框架与JavaScript的高效交互
概述
dioxus-interpreter-js
提供了高性能的JavaScript粘合层,用于解释Dioxus VirtualDom产生的编辑流,并将其转换为实际Web DOM的变更。
这个crate提供了针对web的绑定,并使用sledgehammer来提升性能。
架构
我们使用TypeScript编写绑定,并通过一个非常简单的build.rs配合bun将其转换为JavaScript、压缩并集成到项目中。
并非每个JS片段都会被使用,因此我们将这些片段从核心解释器中分离出来。
理论上,我们可以在浏览器中使用Rust来完成所有这些绑定所做的工作。但实际上,我们希望坚持使用JS以避免在运行LiveView和WebView渲染器时需要WASM构建步骤。我们还希望使用JS来防止在取消事件、上传文件和收集表单输入等操作时出现行为差异。这些细节在两种语言中实现时很难确保1:1的兼容性。
安装
在项目目录中运行以下Cargo命令:
cargo add dioxus-interpreter-js
或者在Cargo.toml中添加以下行:
dioxus-interpreter-js = "0.6.2"
完整示例代码
以下是一个使用dioxus-interpreter-js实现Dioxus与JavaScript交互的完整示例:
use dioxus::prelude::*;
use dioxus_interpreter_js::JsInterpreter;
fn main() {
// 初始化Dioxus应用
dioxus::web::launch(App);
}
fn App(cx: Scope) -> Element {
// 创建JS解释器实例
let interpreter = JsInterpreter::new(cx);
// 定义一个从JS调用的回调
let js_callback = move |msg: String| {
println!("从JS接收到的消息: {}", msg);
};
// 注册回调到解释器
interpreter.register_callback("rust_callback", js_callback);
// 渲染UI
cx.render(rsx! {
div {
h1 { "Dioxus与JS交互示例" }
button {
onclick: move |_| {
// 调用JS函数
interpreter.call_js("alert", "Hello from Rust!".to_string())
.expect("调用JS函数失败");
},
"调用JS alert函数"
}
button {
onclick: move |_| {
// 执行JS代码
interpreter.eval("console.log('JS代码执行成功!')")
.expect("执行JS代码失败");
},
"执行JS代码"
}
script { "
// 定义JS端的函数,可以被Rust调用
function alert(msg) {
window.alert(msg);
}
// 定义JS端的函数来调用Rust回调
function callRustCallback() {
if (window.rust_callback) {
window.rust_callback('Hello from JavaScript!');
}
}
" }
button {
onclick: move |_| {
// 调用JS函数,该函数会回调Rust
interpreter.call_js("callRustCallback", ())
.expect("调用JS回调函数失败");
},
"触发JS回调Rust"
}
}
})
}
扩展示例
以下是一个更完整的示例,展示了更复杂的交互场景:
use dioxus::prelude::*;
use dioxus_interpreter_js::JsInterpreter;
use serde_json::json;
fn main() {
dioxus::web::launch(AdvancedApp);
}
fn AdvancedApp(cx: Scope) -> Element {
let interpreter = JsInterpreter::new(cx);
let count = use_state(cx, || 0);
// 注册多个回调
interpreter.register_callback("increment", |_: ()| {
count.with_mut(|c| *c += 1);
});
interpreter.register_callback("show_data", |data: String| {
println!("接收到的数据: {}", data);
});
cx.render(rsx! {
div {
h1 { "高级交互示例 - 当前计数: {count}" }
button {
onclick: move |_| {
// 传递复杂JSON数据到JS
let data = json!({
"name": "Dioxus",
"version": "0.3",
"features": ["interpreter", "js-binding"]
});
interpreter.call_js("processData", data.to_string())
.expect("调用JS函数失败");
},
"发送JSON数据到JS"
}
button {
onclick: move |_| {
// 从JS获取数据
let result = interpreter.eval("getComplexData()")
.expect("执行JS代码失败");
println!("从JS获取的结果: {:?}", result);
},
"从JS获取数据"
}
script { "
// 存储数据的JS对象
const appData = {
users: [],
config: {
theme: 'dark'
}
};
// 处理来自Rust的复杂数据
function processData(jsonStr) {
const data = JSON.parse(jsonStr);
console.log('处理数据:', data);
if (window.show_data) {
window.show_data(`处理完成: ${data.name} v${data.version}`);
}
}
// 返回复杂数据给Rust
function getComplexData() {
return JSON.stringify({
timestamp: Date.now(),
data: appData
});
}
// 全局函数供外部JS调用
window.incrementCounter = function() {
if (window.increment) {
window.increment();
}
};
" }
button {
onclick: move |_| {
// 调用JS全局函数
interpreter.eval("window.incrementCounter()")
.expect("执行JS代码失败");
},
"通过JS全局函数增加计数"
}
}
})
}
贡献
- 在我们的问题跟踪器上报告问题
- 加入discord并提问!
许可证
本项目采用MIT许可证授权。
除非您明确声明,否则您有意为Dioxus提交的任何贡献都应按照MIT许可证授权,无需任何附加条款或条件。
Rust跨平台JS解释器库dioxus-interpreter-js的使用指南
简介
dioxus-interpreter-js
是一个为 Dioxus 框架设计的 JavaScript 解释器库,它允许在 Rust 应用中高效地执行 JavaScript 代码并与 Dioxus 组件交互。这个库特别适合需要在 Rust 应用中嵌入 JavaScript 功能,或者在 Dioxus 应用中动态执行 JS 代码的场景。
主要特性
- 跨平台支持(Windows、macOS、Linux)
- 与 Dioxus 框架深度集成
- 高效的内存管理和执行性能
- 支持 Rust 与 JavaScript 之间的双向数据传递
- 提供安全的执行环境
安装
在 Cargo.toml 中添加依赖:
[dependencies]
dioxus-interpreter-js = "0.3"
dioxus = { version = "0.4", features = ["desktop"] }
基本使用方法
1. 初始化解释器
use dioxus_interpreter_js::Interpreter;
fn main() {
let mut interpreter = Interpreter::new().expect("Failed to create interpreter");
// 执行简单的JS代码
let result = interpreter.eval("2 + 2").expect("Evaluation failed");
println!("Result: {}", result); // 输出: Result: 4
}
2. 与 Dioxus 组件交互
use dioxus::prelude::*;
use dioxus_interpreter_js::Interpreter;
fn App(cx: Scope) -> Element {
let interpreter = use_ref(&cx, || Interpreter::new().unwrap());
cx.render(rsx! {
button {
onclick: move |_| {
let result = interpreter.write().eval("Math.random()").unwrap();
println!("Random number from JS: {}", result);
},
"Get Random Number from JS"
}
})
}
3. 在 JS 中调用 Rust 函数
use dioxus::prelude::*;
use dioxus_interpreter_js::{Interpreter, JsValue};
fn App(cx: Scope) -> Element {
let interpreter = use_ref(&cx, || {
let mut interpreter = Interpreter::new().unwrap();
// 注册Rust函数到JS环境
interpreter.add_function("rustAdd", |args: Vec<JsValue>| {
let a: f64 = args[0].as_number().unwrap();
let b: f64 = args[1].as_number().unwrap();
Ok(JsValue::Number(a + b))
}).unwrap();
interpreter
});
cx.render(rsx! {
button {
onclick: move |_| {
let result = interpreter.write().eval("rustAdd(5, 3)").unwrap();
println!("5 + 3 = {}", result); // 输出: 5 + 3 = 8
},
"Call Rust from JS"
}
})
}
高级用法
1. 传递复杂数据结构
use dioxus_interpreter_js::{Interpreter, JsValue};
use serde_json::json;
fn main() {
let mut interpreter = Interpreter::new().unwrap();
// 创建复杂JS对象
let user_data = json!({
"name": "Alice",
"age": 30,
"hobbies": ["reading", "coding"]
});
// 将JSON转换为JS值
let js_value = JsValue::from_json(&user_data).unwrap();
// 设置全局变量
interpreter.set_global("userData", js_value).unwrap();
// 在JS中使用这个变量
let result = interpreter.eval("userData.hobbies.join(', ')").unwrap();
println!("Hobbies: {}", result); // 输出: Hobbies: reading, coding
}
2. 错误处理
use dioxus_interpreter_js::{Interpreter, JsError};
fn main() {
let mut interpreter = Interpreter::new().unwrap();
match interpreter.eval("notDefinedFunction()") {
Ok(result) => println!("Result: {}", result),
Err(JsError::EvaluationError(e)) => {
eprintln!("JS evaluation error: {}", e);
},
Err(e) => {
eprintln!("Other error: {:?}", e);
}
}
}
3. 性能优化技巧
对于频繁执行的JS代码,可以预编译:
use dioxus_interpreter_js::{Interpreter, JsScript};
fn main() {
let mut interpreter = Interpreter::new().unwrap();
// 预编译JS代码
let script = JsScript::compile("function add(a, b) { return a + b; }").unwrap();
// 执行预编译的脚本
interpreter.execute_script(&script).unwrap();
// 多次调用更高效
for i in 0..10 {
let result = interpreter.eval(&format!("add({}, {})", i, i*2)).unwrap();
println!("{} + {} = {}", i, i*2, result);
}
}
完整示例Demo
下面是一个结合了基本使用和高级功能的完整示例:
use dioxus::prelude::*;
use dioxus_interpreter_js::{Interpreter, JsValue, JsScript};
use serde_json::json;
fn main() {
// 初始化Dioxus应用
dioxus::desktop::launch(App);
}
fn App(cx: Scope) -> Element {
// 创建解释器实例
let interpreter = use_ref(&cx, || {
let mut interpreter = Interpreter::new().unwrap();
// 注册Rust函数
interpreter.add_function("greet", |args: Vec<JsValue>| {
let name = args[0].as_string().unwrap();
Ok(JsValue::String(format!("Hello, {}!", name)))
}).unwrap();
// 预编译常用函数
let script = JsScript::compile(
"function calculateDiscount(price, discount) { return price * (1 - discount); }"
).unwrap();
interpreter.execute_script(&script).unwrap();
interpreter
});
// 设置全局数据
use_effect(&cx, (), |_| {
let mut interpreter = interpreter.write();
let product_data = json!({
"name": "Rust Book",
"price": 39.99,
"inStock": true
});
let js_value = JsValue::from_json(&product_data).unwrap();
interpreter.set_global("product", js_value).unwrap();
async move {}
});
cx.render(rsx! {
div {
h1 { "Dioxus JS Interpreter Demo" }
// 调用预编译的JS函数
button {
onclick: move |_| {
let result = interpreter.write().eval(
"calculateDiscount(product.price, 0.2).toFixed(2)"
).unwrap();
println!("Discounted price: ${}", result);
},
"Calculate 20% Discount"
}
// 调用注册的Rust函数
button {
onclick: move |_| {
let result = interpreter.write().eval(
"greet(product.name)"
).unwrap();
println!("{}", result);
},
"Greet Product"
}
// 错误处理示例
button {
onclick: move |_| {
match interpreter.write().eval("undefinedFunction()") {
Ok(result) => println!("Result: {}", result),
Err(e) => eprintln!("Error: {:?}", e),
}
},
"Trigger Error"
}
}
})
}
注意事项
- 解释器实例不是线程安全的,需要在单线程中使用
- 大量JS执行可能会影响性能,建议将计算密集型操作放在Rust端
- 注意防范注入攻击,不要执行不可信的JS代码
- 内存管理需要特别注意,避免循环引用
dioxus-interpreter-js
为 Dioxus 应用提供了强大的 JavaScript 集成能力,使得在 Rust 应用中嵌入动态脚本功能变得简单高效。