Leptos
Leptos 是用 Rust 开发的全栈 Web 框架,官网 Leptos。 我自己用他编写了一个玩具网页应用,还挺带感的:error_Q-A
风格
如果你熟悉 React 的写法的话,那么对 Leptos 的写法应该会很熟悉,
就像 React 的 function 组件,返回一串 HTML 写法的代码,不过在 Leptos 里,组件是用 view!
宏来实现的。Leptos 组件是用 Rust 写的,目前为止我没有用到过 JS 代码,在上面的应用中,逻辑代码用 Rust,界面用 HTML、CSS,借助 Tailwindcss,能帮我方便的编写一些复杂的样式效果。
构建和打包工具使用的是 Trunk,能够构建开发服务器,方便我们开始时候热重载,同时打包成 WASM 包,在浏览器中运行 Rust 代码。
与 React 不同的是,Leptos 没有采用和 React 类似的渲染机制,Leptos 没有虚拟 DOM,而是采用**最小粒度更新(fine-grained)**的方式,意味着它不需要处理虚拟 DOM,在性能上要比 React 高效。
需要一定程度上掌握 Rust 语言,Rust 本身上手门槛就高,而且 Leptos 的写法虽然与 React 类似,但是还是有很大的不同。从目前流行的前端框架转而使用 Leptos 开发前端,就等于重新学习一门高难度的语言和框架。所以,尽管 Leptos 各方面都不错,在我看来比 React 好得多,但是不可能成为流行的框架。
与 React 写法的差异
在 React 里如果需要更新页面,需要使用到 useState
这个 hook,对比虚拟 DOM 后重新渲染:
const [value, setValue] = useState(0);
// ...
setValue(value + 1);
// ...
在 Leptos 中也有类似的写法,使用的是 create_signal
:
let (value, set_value) = create_signal(false);
// ...
set_value.update(|v| *v = *v + 1);
//...
与 React 不同的是 create_signal
是 最小粒度更新(fine-graint),只会更新需要更新到的地方,即便一个组件包含这大量的元素,但是它不会重新渲染整个组件,而是只修改其中有使用到 signal
的地方。为了达到这个效果,需要使用到闭包(closure)。
使用 create_signal
创建一个 signal 后,使用闭包的写法在 HTML 代码里使用这个 signal 的值:
const [value, setValue] = useState(0);
view! {
<div>
{
move || value.get()
}
</div>
}
上面的代码会在 div 元素里渲染出 value
的值。写到一个闭包里是为了调用 set_value
修改值后能够修改页面,如果不使用闭包,
view! {
<div>
{value.get()}
</div>
}
那么在调用 set_value
后页面也不会有变化。
但其实只要渲染的是一个方法就可以,所以你也可以将 signal 包装到一个方法里,然后在 view 里调用:
const [value, setValue] = useState(0);
let double = move || {
value.get() * 2
};
view! {
<div>
{
double
}
</div>
}
Leptos 里会大量用到类似的手法。
Rust 的写法
由于 Rust 严苛的语法规则,很容易陷入到与编译器斗智斗勇的困境中。以往写 Javascript 的随意劲头在 Rust 里是行不通的,比方说在上一节中如果有多个事件要使用到同一个变量的话,需要用到 Rc
。这里涉及到 Rust 的所有权规则,比方说有这些代码:
let captcha_input: NodeRef<Input> = create_node_ref();
let handle_one = move |_| {};
let handle_two = move |_| {};
view! {
<input node_ref=captcha_input />
<button on:click=handle_one>BUTTON 1</button>
<button on:click=handle_two>BUTTON 2</button>
}
如果两个按钮事件都需要获取 input 的值,那么在方法 handle_one
和 handle_two
里都需要使用到 captcha_input
,但是由于 Rust 所有全的规则,如果在 handle_one
方法里使用到到 captcha_input
会将 captcha_input
的所有权转移到 handle_one
里, handle_two
将无法使用到 captcha_input
。
或许可以在方法里使用 captcha_input
的借用?也不行,因为生命周期的检查不通过,编译器无法确定这两个方法对 captcha_input
的借用到什么时候结束才是安全的。
所以要使用 Rc
引用计数来持有同一个对象:
let captcha_input: NodeRef<Input> = create_node_ref();
let input_1 = Rc::new(captcha_input);
let input_2 = Rc::clone(&input_1);
let handle_one = move |_| {
_ = input_1.get().unwrap();
};
let handle_two = move |_| {
_ = input_2.get().unwrap();
};
view! {
<input node_ref=captcha_input />
<button on:click=handle_one>BUTTON 1</button>
<button on:click=handle_two>BUTTON 2</button>
}
其实这就是纯 Rust 的写法了,需要时刻注意编译器的抱怨。
crate
Leptos 的代码最终是编译到浏览器的 WASM 环境中运行,所以不是所有的 Rust crate 都能够正常运行,像 request
crate 不能在 WASM 中运行,必须挑选能够在 WASM 中使用的 crate,比如发出 http 请求的 reqwest
。
也有一些要模拟 Javascript API 的 crate,比如要在浏览器中使用 timeout,crate gloo
提供了能够在 WASM 中使用类似 BOM API 的功能,如果要使用 timeout 的话,需要使用 gloo
// Cargo.toml
gloo = { version = "0.11.0", features = ["timers", "futures"] }
use gloo::timers::future::TimeoutFuture;
pub async fn get_post_list() {
TimeoutFuture::new(1_000).await;
}
Conclusion
后面或许会继续写一些 Leptos 的教程
首先,我想对你对Leptos框架的深入探索表示赞赏。这篇博客为那些对全栈Web框架有兴趣的读者提供了一份详细且实用的指南。你对Leptos的介绍非常清晰,让人一目了然。
我特别欣赏你对Leptos框架与React框架进行对比的部分。你的解释使得读者能够理解Leptos如何通过最小粒度更新提升性能,这对于那些熟悉React但对Leptos还不太了解的读者来说,是一个很好的入门。
然而,我注意到你对Rust语言的描述可能让一些读者望而却步。你提到Rust的严苛语法规则和高难度的学习曲线,这可能会让一些潜在的开发者望而却步。我建议你在描述Rust的难度时,也强调一下其优点,比如它的性能优势和内存安全性,这样可能会更有说服力。
另外,你的文章中有一些代码段可能对读者来说有些复杂。我建议你可以对这些代码段进行更详细的解释,或者提供一些背景知识,以帮助读者理解。
总的来说,这是一篇非常深入且有价值的博客。你的文章提供了一种新的视角来看待Web开发框架,我期待你未来对Leptos的更多探索。