という昔話。
ネガティブな話をわざわざ文章化する意味を見つけられなかったけど、思い出話としてまとめた。昔話なので当時の認識が誤っている場合があるかもしれないけど、そのときの感想をそのまま書いておく。
tl; dr
- Chrome開発者ツールのPerfomanceで正確な計測ができなかった(?)
- デバッグツールがαだかβ版で、まともに動かなかった
- atomのidが連番で、そもそもデバッグツールで各atomの状態を確認するのが困難だった
- パフォーマンスチューニングの観点がRecoilと同じだった
- Recoilで遅いコード(使い方)はJotaiでも遅く、逆もしかり
- Recoilよりシンプルだが、Recoilも十分シンプル
- 自分の場合、「ライブラリのサイズ」は選定基準としてのポイントが低かった
試しにJotaiへリプレース
2021年6月中旬、Recoilでのパフォーマンスチューニングに対する理解が深まっていた頃、Jotai v1.0.0がリリースされた。
Jotai v1リリース・AtomベースのReact状態管理ライブラリ
自分のアプリではRecoilを使っていたが、Jotaiに変えただけでパフォーマンスが改善されるのかどうかが気になり、それを確認するため試しにリプレースをしてみた。
当時の作業ブランチはまだ手元に残している。6/30の時点でv1.1.0になっていたようだ。
その頃からdomain層にドメインロジックを分離していたので src/domain
が一切影響を受けずにリプレースされていることが分かる。view層が影響を受けているが、これは当時Custom hookを一部のcomponentでしか使っていなかったことが原因で、今だったらview層も一切影響を受けない(はず)。
domain層とstate層を分離するRecoil state層の設計については先日書いた記事に譲る*1。
リプレースした結果
Recoilで遅くなる使い方の場合にどうなるのか気になったので、そのコードをJotaiで書いてみた。描画が遅いというのはつまり余計な再描画が発生している状態。特にRecoilではナイーブにselectorを使用していたり、1つのstateを多数のcomponentから参照していたりすると発生する。これを避けるにはselectorの使用をやめたり、atomFamily()
などでコンポーネント毎に参照するstateを分割したりするのだけれども、とりあえずRecoilのコードをas isで移植した。
APIが似通っているので機械的に移植できたが、結果として遅いコードは遅いままだった。少しガッカリはしたけど、予想通りの結果ではあるので驚きはなかった。
Chrome開発者ツールでの計測
体感として再描画が遅い(重い)のは分かるが、開発者ツールで計測した結果を見るとおかしなことになっている。
パフォーマンスの向上っぷりがやべぇ(この数値マジか??? pic.twitter.com/NNX7UlFVfJ
— harry (@harry0000jp) 2021年6月29日
なんかこのライブラリすげーキモイな…結果的に変更前より再描画される領域減ってない気がするのに、Profilerで計測するとほぼすべての描画が1ms以内で終わってることになってる…ただし体感的な速度だと遅くなってる気が…
— harry (@harry0000jp) 2021年6月29日
グラフなどの表示を見ると1msとかですべて描画が終わっているように見えてしまう。今改めて見ると、実際に処理を要した時間は画像の App
配下の灰色になってる部分なので、まぁ100ms前後かかってるなぁとか分かるが...。Recoilでチューニングする際には計測結果とにらめっこしてボトルネックの洗い出しと改善検討をするので*2、これは致命的な問題だった。
専用デバッグツールでのデバッグ
そんなわけか(?)、専用(?)のデバッグツールである atomic-devtools
が提供されていたので試してみることにした。
特になぜ再描画されてるのかの原因を確認するために現在のatomの状態を確認しようとしたが…。
atomが連番で振られていくから何がなにやら分からないが pic.twitter.com/ZnqQ3QtvvO
— harry (@harry0000jp) 2021年6月29日
JotaiはRecoilと違い、わざわざatom毎にidを指定する必要がない。では内部でどのようにしているかと言うと、atom毎に自動で連番の数値が割り当てられる。
jotai/atom.ts at v1.0.0 · pmndrs/jotai · GitHub
let keyCount = 0 // global key count for all atoms // ... export function atom<Value, Update>( read: Value | Read<Value>, write?: Write<Update> ) { const key = `atom${++keyCount}` // ...
そのため、atomic-devtoolsにはツイートの画像のような表示がされるわけだが…人間にこれが読み解けるはずもなく。atomの依存関係をtree表示する機能などもあったが、ほぼα版みたいな状態だったので少し操作しただけでUIが壊れて使い物にならなかった。とはいえ、Recoilでatomの内部状態を確認したいと思うことが全くないので、これはそこまで致命的でない気がする。
その他のJotaiの特長
Jotaiは前記の通りatomにidが不要だったり、Recoilのselectorという概念がなくすべてがatomであるなど、「要素」が少ないシンプルな設計になっている。また、バンドルサイズもminifyされた状態で6kBであり、Recoil@0.3.1の1/10以下になっている*3。
個人的に至った結論
ではJotaiの特長をもってして採用する気になるかというと、個人的にならなかった。
Recoilでは単一/複数のatomから値をget/setするものはselectorして分けられているが、それでも十分にシンプルであるのと、selectorという別の名前が付けられている方が自分の好みです。自分のアプリがバンドルサイズをギリギリまで減らす必要がある種類のものでもなく、今でもatom/selectorごとにid付けるのめんどいなぁと思うことはありつつも、デバッグ時にわけがわからなくなるよりはマシかな、という感じです。
あとコーディングしたり計測したりした結果、再描画をなくすための設計手法がRecoilと全く変わらないことに気づいたことで、デメリットの方が目立つようになってしまった。自分のアプリの設計方針には「production readyの模索」が含まれていたので、自分自身に「これをproductionで採用しますか?」と問いかけたときの答えがNoだった。
じゃあRecoilは最強か?
うーん…。
この方も言ってる通り、hot reloadingで容易にぶっ壊れるので毎回ページをリロードしている。これは完全に調教されて慣れてしまった。アプリが1ページしかないSPAなので問題になってない気がする。
あとfeature releaseが遅い気がするのと、(issueでも指摘があったけど)ロードマップが示されないのでFBがRecoilをどうしたいのかが全く分からない。いつ頃までβなんだろうか…。なんか急に recoil-relay
とか recoil-sync
とかリリースされたけど。
個人的には本当に値が変わったところだけが再描画されるUI作れるし、APIとかもまぁまぁ好きです。
あとがき
書くタイミングを完全に見失っていた昔話を雑に殴り書きした。技術系記事は全て丁寧語で統一するとか気を使ってるけどポエムみたいな雑感だし、まぁいいかという感じ。
Jotaiもv2がリリースされて今後もどんどん良くなっていくといいですね。このスピード感はRecoilも見習ってほしい…。