React状態管理ライブラリ Jotai の Derived Atom とは #JotaiFriends
Derived=派生とは何でしょうか。
ちなみに、Derived AtomはJotaiの特徴を表す機能だと思っています。それを踏まえて見ていきましょう。
atomはatomから生成できる
わざわざいうことでもないかも知れませんが、噛み砕きます。初歩的な使い方として、atomは初期値を与えて定義しますが、read/write関数を与えて定義することもできます。その時に、元となるatom
を使うことが出来ます。
簡単な例
誕生日を定義してみます。これはどこかで使うとしてexportもします。
export const birthdayAtom = atom(new Date(2022, 0, 1));
誕生日がわかるなら年齢が計算できますね。そんな時、birthdayAtomを派生元としてageAtomが定義可能です。
export const ageAtom = atom((get) => {
const date = get(birthdayAtom);
return getAge(date);
});
年齢は誕生日から計算されるべきで、年齢そのものを書き換えさせるべきではないですね。
このように、何かのatomを元に値の見せ方を変えたい時にread関数のみを与えて使います。
コンポーネント内で誕生日からgetAge(date)で年齢計算する、なんてことはせず年齢用のatomを作っちゃえば使いたい時に好きなだけ使えます。
元のatomは使わない例
(https://jotai.org/docs/guides/composing-atoms より)
const textAtom = atom('hello')
export const textUpperCaseAtom = atom(
(get) => get(textAtom).toUpperCase(),
(_get, set, newText) => set(textAtom, newText)
)
上記の例では、textAtomは一種のストアとして扱っています。readする際に見せ方を操作(toUpperCase)していますね。writeでは新しい値を受け取ってsetしているだけです。
Reactの外の値と繋げる例
derived atomのwrite関数内ではサイドエフェクトのある処理を記述できます。自由です。
ドキュメントではlocalStorageと繋げる例を紹介してくれています。
(https://jotai.org/docs/guides/composing-atoms より)
const baseAtom = atom(localStorage.getItem('mykey') || '')
export const persistedAtom = atom(
(get) => get(baseAtom),
(get, set, newValue) => {
const nextValue = typeof newValue === 'function'
? newValue(get(baseAtom))
: newValue
set(baseAtom, nextValue)
localStorage.setItem('mykey', nextValue)
}
)
オフトピ:write関数の第3引数を柔軟にする工夫
ドキュメントやサンプルを眺めているとwrite関数内に以下のような記述が見られます。(上記のwrite関数より)
const nextValue = typeof newValue === 'function'
? newValue(get(baseAtom))
: newValue;
よく、const [state, update] = useState()
のupdate()をupdate(state + someValue)
や update((s) => s + someValue)
と書きますよね。useAtom()[1]
のupdate関数で同じことをするために上記のようにします。
Jotaiっぽさを出す
stateをreducerスタイルでupdate処理することはそれなりにあるかと思います。atomWithReducerはutilsから提供されていますが、Jotaiっぽさを出すにはこれは当てはまりません。
どういうことでしょうか。
インクリメント・デクリメントするcountAtomをreducerスタイルとjotaiスタイルで見比べてみます。
まずはreducerスタイルです。はい、なるほど。
export const countAtom = atomWithReducer(0, (prev, action) => {
if (action === 'INC') {
return prev + 1
}
if (action === 'DEC') {
return prev - 1
}
throw new Error('unknown action')
})
次にjotaiスタイルです。
const baseAtom = atom(0) // do not export
export const countAtom = atom((get) => get(baseAtom)) // read only
export const incAtom = atom(null, (_get, set) => {
set(baseAtom, (prev) => prev + 1)
})
export const decAtom = atom(null, (_get, set) => {
set(baseAtom, (prev) => prev - 1)
})
countAtomはread-onlyとして定義し、action === 'INC'
やaction === 'DEC'
をそれぞれwrite-onlyとしてincAtom,decAtomを定義しています。これがJotaiっぽさになります。
利点として紹介されているのは、コードスプリットやLazyローディングに活かせる点です。
まとめるほどもないのでこれにて終わりますが、derived atomはJotaiの特徴でatomはatomから簡単に作ることができる、と覚えてもらえればいいかなと思います。
Jotaiの紹介特集について
この記事はJotai FriendsによるJotai紹介特集記事の1つです。記事一覧はこちらからどうぞ。
Jotai Friendsとは
いちJotaiファンとして、エンジニアの皆さんにもっとJotaiを知ってもらって使ってもらいたい、そんな思いから立ち上げたのがJotai Friendsです。
現在まだまだ準備中ですが今後ともよろしくお願いします!
(ご興味持っていただけた方は是非jotaifriends.devにてEメールアドレスのご登録をお願いします🙏)
Jotai Friends のテックブログです。 React状態管理ライブラリJotai(jotai.org)を新たな選択肢に。 エンジニアの皆さんが安心してJotaiを採用できるように支援します。 Powered by twitter.com/t6adev
Discussion