👻

React状態管理ライブラリ Jotai の Derived Atom とは #JotaiFriends

2022/01/18に公開

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スタイルです。はい、なるほど。

reducer-style.js
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スタイルです。

jotai-style.js
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です。

https://jotaifriends.dev/

現在まだまだ準備中ですが今後ともよろしくお願いします!
(ご興味持っていただけた方は是非jotaifriends.devにてEメールアドレスのご登録をお願いします🙏)

Jotai Friends

Discussion