ES6からJavaScriptに加わったWeakMapですが、Reactと組み合わせて使ってみても意外と便利かなと思い始めました。
WeakMapとは
WeakMapも、単なるオブジェクトやWeakでないMapと同様に、キーと値の組み合わせを管理するための仕組みです。ただ、
-
WeakMapがキーにできるのは、関数・DOMノード・配列などを含め、オブジェクトだけ(プリミティブは使えない) - 今あるキーの一覧を取得する方法がない
- キーのオブジェクトがガベージコレクトされると、
WeakMap内のキー・値ペアも消滅する
というような仕組みになっています。ふつうのMapと違って、入れておいても「キーが」ガベージコレクションされうる、という意味でのWeakです。
よく言われる使いみち
よくWeakMapの使う道として言及されるのは、
- jQueryの
.dataと同様な、DOMノードにデータを紐付けるために使う -
WeakMapインスタンス自体をモジュールに封じきることで、オブジェクトに対してpackage private的なデータの置き場に使う
などです。確かに、いまのところJavaScriptにprivateはありませんので、これもこれでありなのは間違いなさそうです。
Reactとの組み合わせ
もちろん、Reactで「DOMノードと値を紐付ける」なんて操作をやっても誰も嬉しくならないです。ただ、Reactの中では破壊的変更に頼らないデータ管理が推奨されていますので、この観点から考えれば、特定のオブジェクトに値を結び付けられるWeakMapは便利に運用できます。
propsから計算したデータのキャッシュ
時折、renderすべきデータが、propsに対して複雑な計算(典型的な例としては、配列の絞り込み・ソートなど)を行ったものとなる、という場面があります。もちろん、「毎回計算する」でも結果は正しくなるのですが、毎回不要な計算をするオーバーヘッドが積もってきてしまいます。
そして、「stateに計算結果を置いておく」という手段もありますが、それもpropsの更新と同期させるのが煩雑となるということで、Reactの公式ブログでも、「別途でキャッシュの仕組みを入れる」ことを推奨しています。
当該のブログでは、memoize-oneとして「同じオブジェクトである間キャッシュする」という仕組みとなっているため、キャッシュ関数自体をコンポーネントインスタンスに紐付ける必要が出てきていますが、WeakMapであれば全部まとめてキャッシュを管理しても全く問題ないので、関数コンポーネントに対しても運用できます。
イベントハンドラ
Reactでコンポーネントを何階層も重ねていくと、「<Parent>で特定の動作を起こすためのハンドラ関数を下に引き回していくけれど、実際にハンドラに渡す値が供給されるのは孫コンポーネント」というような場面もよくあります。このような場合、孫コンポーネントの側でonClick={() => doParentAction(param)}のようにバインドすると、renderごとに無名関数が作り直しとなって、非効率になります。
ここでも、キーを親から降りてくるdoParentActionとしたWeakMapを作れば、関数コンポーネントでもキャッシュが可能ですし、破棄の心配もありません。
注意点
上記のように、キャッシュとして「きちんと消えてくれる」WeakMapが必要な場合、Polyfillでは実現できません。IE 10以下には本物がないので、そこでは動かせないのは要注意です。