RecoilからJotaiの移行にjotai-recoil-adapter便利でした
まとめ
RecoilからJotaiに段階的に置き換えるのにjotai-recoil-adapterは便利でしたよという話
また、使った際に不具合があったため、そのリマインドもしておきます。
前提
- クライアントサイドの状態管理にrecoilを使っていた
- recoilはすでにメンテナンスされていない
- recoil起因でメモリリークが発生していた
- atomに大きいオブジェクトを繰り返しsetしていたところ、古い値がGCされず残り続け、メモリを大量消費していた
そもそもrecoilがメンテされてないため置き換えるべきでしたが、結果としてメモリリークの問題も解消され良かったです。
置き換え方法の判断と選定
すべてのRecoilの記述を一気にJotaiに置き換えるのは困難(面倒)だったため、移行をサポートするライブラリを探しました。
-
jotai-recoil
jotaijsから提供されている。recoilのatomをjotaiに持ち込むもの -
jotai-recoil-adapter
jotaiの上にrecoilと互換なapiを構築したもの
段階的移行を考えるなら前者も候補ですが、今回は主目的がメモリリークの解消のため、後者を試しました。
いくつかオプション周りで小さい修正をしたもののほとんどそのまま動き、メモリリークの問題も回避できました。
jotaiとjotai-recoil-adapterの併用
併用の際の不具合
実は、jotai-recoil-adapterはjotaiと併用するする際に不具合があります。
jotai-recoil-adapterで作ったatomやselectorを、jotai側から正しく参照できません。
import { atom as recoilAtom } from 'jotai-recoil-adapter'
import { atom as jotaiAtom } from 'jotai'
const anAtom = recoilAtom({ key: "hoge", default: 3 })
const derivedAtom = jotaiAtom( get => get(anAtom) )
// ↑型は問題ないが、anAtomの値の更新がこちらに反映されない
原因を理解するために必要な前提知識
jotaiのatomは、いうなれば「参照」のようなもので、この中には値の中身は存在しません。外部のstoreに値が存在し、WeakMapで保存されています。
その値を取り出すためのkeyとしてatomを使います。
see: https://jotai.org/docs/guides/core-internals
特に引数に指定しなければ、defaultのstoreが作成され、storeを明示的に指定しなければこれが使われます。
原因
原因は、jotai-recoil-adapterのビルドに関する設定にありました。
jotai-recoil-adapterはビルドの際にdepsをbundleしているため、依存であるjotaiの中身もbundleされてしまいます。
このため、内部のgetDefaultStoreもbundleされ、外側のJotaiのstoreとは別のstoreで動作します。結果として、Jotaiのatomを参照しようとすると、異なるstoreで動作してしまい、期待通りに動作しないのです。
(期待通りに動かないのに型は間違っておらず、値をsetしたのに反映されない、という壊れ方だったので問題特定に時間を要して大変でした)
おそらく、ライブラリ開発者は、一度にまるごと置き換えるケースを想定していて、素のjotaiと併用するのはユースケースとして想定していなかったのかもしれないです。
(一応issueを上げて聞いてみているのですが、返事がないので現在は特に使っていないのかもしれない)
対応
要するにビルド時にjotaiをexternalに指定すればよいだけですね。
元のリポジトリがアクティブではなかったので、一旦forkして自分で修正してpublishしました。
original:
fixed:
おわり
Recoil使ってたけど置き換えないとなあ…と思っていた方は試してみてはいかがでしょう?
Discussion