Skip to main content
Version: 0.18.0

性能优化与最佳实践

neq_assign#

当组件从它的父组件接收 props 时,change 方法将被调用。除了允许你更新组件的状态,还允许你返回一个布尔类型的值 ShouldRender 来指示组件是否应该响应 props 的更改而重新渲染自身。

重新渲染的开销很大,你应该尽量避免。一个通用的法则是,你只应该在 props 实际更改时重新渲染。以下代码块展示了此法则,如果 props 和先前的 props 不同,则返回 true

use yew::ShouldRender;
#[derive(PartialEq)]struct ExampleProps;
struct Example {    props: ExampleProps,};
impl Example {    fn change(&mut self, props: ExampleProps) -> ShouldRender {        if self.props != props {            self.props = props;            true        } else {            false        }    }}

但是我们可以更进一步!对于任何实现了 PartialEq 的项,可以使用一个 trait 和一个 blanket implementation 将这六行样板代码减少到一行。看看yewtil 是如何为他的 NeqAssign trait实现此功能的。

有效使用智能指针(smart pointers)#

注意:如果不确定本节中使用的某些术语,阅读 Rust Book 关于智能指针 的章节将有助于理解。

为了避免在重新渲染时为了创建 props 而克隆大量数据,我们可以使用智能指针来只克隆指针。如果在 props 和子组件中使用对相关数据的引用,而不是在实际数据,则可以避免克隆任何数据,当需要修改子组件中的数据时,可以在该组件中使用Rc::make_mut来对要更改数据进行克隆和获取其可变引用。

Component::change 可以更好地判断 props 更改时是否需要重新渲染组件。因为可以比较指针指向的地址(即计算机内存中存储数据的位置)而不是比较数据的值;如果两个指针的指向相同地址,则它们指向数据的值一定相同。需要注意的是,相反的情况下不一定是这样!对于基础数据而言,即使基础数据的值相同,但他们的指针地址也可能不同 —— 在这种情况下,您应该比较基础数据本身。

要进行这种比较,您需要使用Rc::ptr_eq而不是PartialEq (使用相等运算符==比较数据时会自动使用 PartialEq)。 Rust 文档有更详细的Rc::ptr_eq信息.

对于不是 Copy类型的数据,这种优化是最有效的。如果您能低成本、甚至毫无成本地拷贝数据,就没必要将那些数据放入智能指针中。而对于Vec, HashMap, 和 String 这种可以包含大量数据的结构,此时使用智能指针往往能带来性能上的提升。

优化效果在值永远不会被子组件更新时达到最佳,如果值很少被父组件更新,那优化效果还会更好。这让Rc<_>s成为包装纯组件属性值的理想选择。

视图函数#

出于代码可读性的原因,将 html! 各个部分的代码迁移到他们自己的函数中通常是有意义的,这样不仅可以避免深层嵌套的 HTML 中出现代码过多缩进的情况,同时也是一种好的设计模式 —— 特别是在构建可组合的应用程序时,因为这样就可以在其他地方调用这些函数,从而减少需要编写的代码量。

纯组件#

纯组件是指不会修改它们自身状态的组件,它们仅展示内容和向普通可变组件传递消息。它们与视图函数不同之处在于他们可以在html! 宏中使用组件语法(<SomePureComponent />)而不是表达式语法({some_view_function()}),并且根据它们的实现,它们可以被记住(这意味着一个函数被调用过后它的结果会被“保存”,当这个函数再次被同样的参数调用时,它将返回第一次的结果,而不是重新计算。) - 使用前面提到的 neq_assign 逻辑来防止因为相同的 props 而重新渲染。

Yew 没有原生支持纯组件或者函数式组件,但是可以通过外部库获取它们。

当函数式组件开发完成后,此部分将会更新#

使用 Cargo Workspaces 进行编译速度优化#

可以说,使用 Yew 的最大缺点是编译时间长。编译时间似乎与 html! 宏块中的代码量相关。对于较小的项目,这通常不是什么大问题,但是对于跨多个页面的 web 应用程序,通常可以将代码拆分为多个 crates 以最大程度地减少编译器要做的工作。

你应该尝试让主 crate 处理路由和页面选择,将所有公用的代码移动到另一个 crate,然后为每一个页面创建一个不同的 crate,其中每个页面可能是一个不同的组件,或者只是一个产生 Html 的大函数。把程序里每个 crate 中的相同的代码提取到一个单独的 crate 中,这样就可以在整个项目中重复使用。在最好的情况下,你将从重新构建所有代码到只重新构建主 crate 和一个页面的 crate。在最糟糕的情况下,当你在“公共” crate 中编辑内容时,你将回到起点:编译所有依赖此公用 crate 的代码,这可能就是除此之外的所有代码。

如果你的主 crate 过于庞大,或者你想在深层嵌套的页面(例如,在另一个页面顶部渲染的页面)中快速迭代,则可以使用一个示例 crate 创建一个更简单的主页面,并在其之上渲染你正在开发的组件。

减少二进制文件的大小#

  • 优化 Rust 代码
    • wee_alloc (使用小型的分配器(allocator))
    • cargo.toml (定义 release profile)
  • 使用wasm-opt优化 wasm 代码

注意:更多有关减小二进制文件大小的信息,请参见 《Rust Wasm Book》

wee_alloc#

wee_alloc 是一个很小的分配器,它比 Rust 二进制文件中通常使用的分配器小得多。用此分配器替换默认分配器可以减小 Wasm 文件大小,但会导致程序速度变慢、内存开销变大。

与减少的文件大小相比,速度和内存的损耗是很小的。更小的文件体积意味着您的页面将加载得更快,因此通常情况下都建议您使用此分配器来代替默认的,除非您的应用程序十分依赖分配器的表现。

// 将 `wee_alloc` 作为全局的 allocator。#[global_allocator]static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;

Cargo.toml#

在你 Cargo.toml 文件中的[profile.release]部分中使用以下设置,可以将 release 版本的文件构建的更小。

[profile.release]# 二进制文件会包含更少的代码panic = 'abort'# 对所有代码库进行优化(优化更好,构建更慢)codegen-units = 1# 优化大小(更加激进)opt-level = 'z'# 优化大小# opt-level = 's'# 对整个程序进行链接时优化(link time optimization)lto = true

wasm-opt#

此外,还可以尝试优化wasm代码的大小。

Rust Wasm Book 中有关于减小 Wasm 二进制文件大小的章节: 缩减 .wasm 大小

  • 使用 wasm-pack (默认情况下会在 release 构建中优化 wasm
  • 直接对wasm 文件使用 wasm-opt
wasm-opt wasm_bg.wasm -Os -o wasm_bg_opt.wasm

参考 yew/examples/ 中“minimum”示例的构建大小#

注意: wasm-pack 同时优化了 Rust 和 Wasm 的代码。 而示例中使用的 wasm-bindgen 没有进行任何 Rust 大小优化。

使用的工具大小
wasm-bindgen158KB
wasm-bindgen + wasm-opt -Os116KB
wasm-pack99 KB

进一步阅读:#