Html.Keyed
前のページでは、仮想DOMがどのように動くのかと、Html.Lazy
モジュールを使って多くの処理を減らすことができるのを学びました。次はHtml.Keyed
を導入し、さらに多くの処理を省く方法を紹介していきます。
データのリストに対して挿入や削除、並び替えといった操作が可能なアプリケーションであるとき、この最適化は特に役に立ちます。
問題
アメリカ合衆国のすべての大統領の一覧があるとしましょう。また、それを名前順、学歴順、資産額の順、出身地順に基いて並び替えができるようにします。
(前のページで説明したような)差分アルゴリズムが長いリストにとりかかると、次のように現在のリストと次のリストから同じ位置の要素を組にして処理を行っていきます。
- 現在のリストのひとつめの要素と、次のリストのひとつめの要素の差分をとる
- 現在のリストのふたつめの要素と、次のリストのふたつめの要素の差分をとる
- ...
しかし、並び替えの順序を変更したときは、これらの組のほとんどが違っていてそれぞれ差分処理が必要になるでしょう! そして、ノードをシャッフルしたときなどでは、DOMに対して大量の操作を行うはめになります。
項目の挿入や削除についても同じような問題があります。100項目あるうちの最初の項目を削除するとしましょう。すべてひとつずつずれていくことになるので、差分を比較する組はいずれも違うものになります。そのため99回の差分を処理し、最後のひとつが削除されることになります。これは良くありません!
解決策
これらすべての問題から救ってくれるのが、Html.Keyed.node
です。これは、『キー』(Key)に従って組を作るようにして、それぞれの要素を他のものと簡単に区別できるようにしてくれるのです。
大統領のサンプルでいうと、コードを次のように書くことができます。
import Html exposing (..)
import Html.Keyed as Keyed
import Html.Lazy exposing (lazy)
viewPresidents : List President -> Html msg
viewPresidents presidents =
Keyed.node "ul" [] (List.map viewKeyedPresident presidents)
viewKeyedPresident : President -> (String, Html msg)
viewKeyedPresident president =
( president.name, lazy viewPresident president )
viewPresident : President -> Html msg
viewPresident president =
li [] [ ... ]
どの子のノードもキーに関連付けられています。要素の順序に基いた組で差分をとる代わりに、キーによる照合に基いた組で比較することができるのです!
これでElmの仮想DOM実装は、リストが並び替えられたことを認識できるようになります。まずElmは、大統領をキーに基づいて照合します。それから、それらの組の差分を処理します。ここではそれぞれの項目にlazy
を使っていますので、それらのすべての処理を省くことができます。素晴らしいでしょう! それからElmは、指定した順序で表示するにはDOMノードをどのように入れ替えればいいかを見つけ出します。結果として、キーを付けたときの処理量はキーを使わなかったときよりも遥かに少なくなるのです。
並び替えでキーが役に立つのはわかりましたが、この最適化が本当に必要になる最もよくある場面というのは他にあります。キーが付けられたノードは、項目の挿入や削除においてなにより重要です。100要素の最初の要素を削除するとき、キー付きのノードを使えばElmの仮想DOM実装がそれを直ちに認識できるようになるのでした。そのため、99回も差分を処理することなく、ひとつの要素を削除するだけで済むのです。
まとめ
通常のアプリケーションで起こる様々な計算と比較すると、DOMを操作するのはとてつもなく遅いです。まずは常にHtml.Lazy
とHtml.keyed
を使うようにしましょう。可能な限りプロファイルをとってみることもお勧めします。このように、プログラムのタイムラインビューを見ることができるブラウザもあります。ページの読み込み、スクリプトの実行、ページのレンダリング、ページの描画などのうち、いったいどこにどれくらい時間を費やしているのかを大まかに教えてくれます。もし時間の10%しかスクリプトの実行に費やされていないのなら、Elmコードそのものが2倍の早さになったとしても、あまり目立った効果は得られないでしょう。単にlazy
やキー付きのノードを追加するだけでDOMの操作が減り、残りの90%の時間という大きな部分から処理を減らすことができるかもしれないのです。