最適化とベストプラクティス
neq_assign
親コンポーネントから props を受け取った際、change
メソッドが呼ばれます。
これはコンポーネントの状態を更新することができるのに加え、コンポーネントが props が変わった際に再レンダリングするかどうかを決める
ShouldRender
という真偽値を返すことができます。
再レンダリングはコストがかかるもので、もし避けられるのであれば避けるべきです。
一般的なルールとして 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
}
}
}
しかし我々は先に進んでいけます!
この 6 行のボイラープレードはPartialEq
を実装したものにトレイトとブランケットを用いることで 1 行のコードへと落とし込むことができます。
こちらにてyewtil
クレートのNewAssign
トレイトを見てみてください。
効果的にスマートポインタを使う
注意: このセクションで使われている用語がわからなければ Rust book は スマートポインタについての章 があり、非常に有用です。
再レンダリングの際に props を作るデ ータを大量にコピーしないために、スマートポインタを用いてデータ自体ではなくデータへの参照だけを
コピーできます。
props や子コンポーネントで関連するデータに実データではなく参照を渡すと、子コンポーネントでデータを変更する必要がなければ
データのコピーを避けることができます。
その際、Rc::make_mut
によって変更したいデータの変更可能な参照を得ることができます。
これにより、props が変更されたときにコンポーネントが再レンダリングされるかどうかを決めるかでComponent::change
に更なる恩恵があります。
なぜなら、データの値を比較する代わりに元々のポインタのアドレス (つまりデータが保管されている機械のメモリの場所) を比較できるためです。
2 つのポインターが同じデータを指す場合、それらのデータの値は同じでなければならないのです。
ただし、その逆は必ずしも成り立たないことに注意してください!
もし 2 つのポインタが異なるのであれば、そのデータは同じである可能性があります。
この場合はデータを比較するべきでしょう。
この比較においてはPartialEq
ではなくRc::ptr_eq
を使う必要があります。
PartialEq
は等価演算子==
を使う際に自動的に使われます。
Rust のドキュメントにはRc::ptr_eq
についてより詳しく書いてあります。
この最適化はCopy
を実装していないデータの型に対して極めて有効なものです。
もしデータを簡単に変更できるのであれば、スマートポインタに取り換える必要はありません。
しかしVec
やHashMap
、String
などのような重たいデータの構造体に対してはスマートポインタを使うことで
パフォーマンスを改善することができるでしょう。
この最適化は値がまだ一度も子によって更新されていない場合に極めて有効で、親からほとんど更新されない場合においてもかなり有効です。
これにより、Rc<_>s
が純粋なコンポーネントに対してプロパティの値をラップする良い一手となります。
View 関数
コードの可読性の理由からhtml!
の部分を関数へと移植するのは意味があります。
これは、インデントを減らすのでコードを読みやすくするだけでなく、良いデザインパターンを産むことにも繋がるのです。
これらの関数は複数箇所で呼ばれて書くべきコード量を減らせるため、分解可能なアプリケーションを作ることができるのです。
純粋なコンポーネント
純粋なコンポーネントは状態を変化せず、ただ中身を表示してメッセージを普通の変更可能なコンポーネントへ渡すコンポーネントのことです。
View 関数との違いとして、純粋なコンポーネントは式の構文({some_view_function()}
)ではなく
コンポーネントの構文(<SomePureComponent />
)を使うことでhtml!
マクロの中で呼ばれる点、
そして実装次第で記憶され (つまり、一度関数が呼ばれれば値は"保存"され、
同じ引数でもう一度呼ばれても値を再計算する必要がなく最初に関数が呼ばれたときの保存された値を返すことができる)、
先述のneq_assign
ロジックを使う別々の props で再レンダリングを避けられる点があります。
Yew は純粋な関数やコンポーネントをサポートしていませんが、外部のクレートを用いることで実現できます。
関数型コンポーネント (a.k.a フック)
関数型コンポーネントはまだ開発中です! 開発状況についてはプロジェクトボードに詳しく書いてあります。
キー付き DOM ノード
ワークスペースでコンパイル時間を減らす
間違いなく Yew を使う上での最大の欠点はコンパイルに時間がかかる点です。
プロジェクトのコンパイルにかかる時間はhtml!
マクロに渡されるコードの量に関係しています。
これは小さなプロジェクトにはそ こまで問題ないようですが、大きなアプリではコードを複数クレートに分割することでアプリに変更が加られた際に
コンパイラの作業量を減らすのが有効です。
一つ可能なやり方として、ルーティングとページ洗濯を担当するメインのクレートを作り、それぞれのページに対して別のクレートを作ることです。
そうして各ページは異なるコンポーネントか、Html
を生成する大きな関数となります。
アプリの異なる部分を含むクレート同士で共有されるコードはプロジェクト全体で依存する分離したクレートに保存されます。
理想的には 1 回のコンパイルでコード全てを再ビルドせずメインのクレートかどれかのページのクレートを再ビルドするだけにすることです。
最悪なのは、"共通"のクレートを編集して、はじめに戻ってくることです:
共有のクレートに依存している全てのコード、恐らく全てのコードをコンパイルすることです。
もしメインのクレートが重たすぎる、もしくは深くネストしたページ (例えば別ページのトップでレンダリングされるページ) で速く繰り返したい場合、クレートの例を用いてメインページの実装をシンプルにしたりトップで動かしているコンポーネントをレンダリングできます。
バイナリサイズを小さくする
- Rust のコードを最適化する
cargo.toml
( release profile を定義 )wasm-opt
を用いて wasm のコードを最適化する
注意: バイナリサイズを小さくするのについてはRust Wasm Bookに詳しく書いてあります。
Cargo.toml
Cargo.toml
で[profile.release]
のセクションに設定を書き込むことでリリースビルドを小さくすることが可能です。
[profile.release]
# バイナリに含むコードを少なくする
panic = 'abort'
# コードベース全体での最適化 ( 良い最適化だがビルドが遅くなる)
codegen-units = 1
# サイズの最適化( よりアグレッシブに )
opt-level = 'z'
# サイズの最適化
# opt-level = 's'
# プログラム全体の分析によるリンク時最適化
lto = true
wasm-opt
更にwasm
のコードのサイズを最適化することができます。
The Rust Wasm Book には Wasm バイナリのサイズを小さくすることについてのセクションがあります: Shrinking .wasm size
wasm-pack
でデフォルトのwasm
のコードをリリースビルド時に最適化するwasm-opt
によって直接wasm
ファイルを最適化する
wasm-opt wasm_bg.wasm -Os -o wasm_bg_opt.wasm
yew/examples/にある例を小さなサイズでビルドする
注意: wasm-pack
は Rust と Wasm のコードへの最適化を組み合わせます。wasm-bindgen
はこの例では Rust のサイズ最適化を用いていません。
使用したツール | サイズ |
---|---|
wasm-bindgen | 158KB |
wasm-bindgen + wasm-opt -Os | 116KB |
wasm-pack | 99 KB |