Jotaiで快適フロントエンド開発

ここ2年ほど、すっかりフロントエンド開発者になっている藤田です。以前、Reactフロントエンドの状態管理ライブラリRecoilについて記事を書きました。

その後どうなったかというと、実はRecoilからJotaiに乗り換えていて、半年ほど経ちましたので、Jotaiについて書きたいと思います。

Jotai

サイトの一行目から「Recoilにインスパイアされた」と言ってるとおり、Recoilの良いところを受け継ぎ、不便なところを無くしたような状態管理ライブラリです。

Jotaiの基本

基本はRecoilとほとんど同じで、React.useStateを便利にしたような感覚で非常に簡単に使えます。3ステップで見てみましょう。

1. Providerで囲む

アプリケーション全体を<Provider>で囲みます。

import {Provider} from 'jotai'

const App: React.FC = () => {
  return (<Provider>
    (アプリケーションのルートコンポーネント)
  </Provider>)
}

2. atomを作る

コンポーネントの外にatomを作ります。このatomをいろんなコンポーネントから参照することで、コンポーネントをまたいだ状態管理が可能になります。

import {atom} from 'jotai'

// 単にstringの値を格納するatom
export const messageAtom = atom('')

// APIで値を取得して格納するatom
export const loginUserAtom = atom(async (): Promise<LoginUser> => {
  const session = await backend.getSession()
  return session.loginUser
})

3. useAtomでgetterとsetterを得る

コンポーネント中でReact.useStateと同じように使えます。

import {useAtom} from 'jotai'

// <Suspense>で囲む前提。
const MyComponent: React.FC = () => {
  const [message, setMessage] = useAtom(messageAtom)
  // Promiseを格納するatomからはPromise解決後の値が返る。
  const loginUser = useAtomValue(loginUserAtom)
  return (
    <div>Hello, {loginUser.name}!
    <input type="text" 
           value={message} 
           onChange={(e) => {
             setMessage(e.target.value)
           }} />
    </div>
  )
}

JotaiがRecoilと同様に良いところ

前の記事「Recoilで快適フロントエンド開発」にReduxとの比較を念頭において書いた通りです。

  • コンポーネントをまたぐ状態ストア「atom」を自由に複数作れる
  • ReduxやRedux toolkitより圧倒的に少ないコードで書ける
  • 最初から非同期に対応していて、ネットワークAPIを呼ぶような挙動も簡単に作れる
  • React Hooks、Suspense前提
  • core APIが少ないのでドキュメントは簡潔で読みやすいです。痒いところに手が届くjotai/utilsライブラリもドキュメント類が揃ってます。

JotaiがRecoilよりも良いところ

開発が継続している

Jotaiに乗り換えた後で知ったのですが、残念ながら、2022年10月にRecoilの主要メンテナの@drarmstr氏がMeta社の一斉レイオフで解雇されたらしく、Recoilは開発が止まってしまいました。2023年4月に同氏が小さなアップデートをリリースしていますが、code-frequencyを見る限りほとんど止まっています。今後”experimental”フェーズが取れる等の大きなニュースは起こらなそうです。

2023年7月現在、Jotaiのダウンロード数はRecoilを追い抜きつつあります。Weekly downloadは50万を超え、開発もコンスタントに続いています

バンドルサイズが小さい

ライブラリ npmのzipファイルサイズ
Jotai 2.7KB
Recoil 23.5KB

Recoilよりかなり小さいですね。

atomしかない

これは良し悪しではなく単なる違いですが、JotaiのatomはRecoilのatomとselectorを兼ねています。もしかしたら結果的にJotaiの省スペースに貢献しているかもしれません。

keyが要らない

Recoilのatomやselectorを作るには、アプリケーション中で一意なkeyという文字列をおまじないのようにセットする必要があって、たくさん作っているとだんだんkeyを考えるのが億劫になります。

import {atom} from 'recoil'

// Recoilのatom宣言
const loginUserAtom = atom<LoginUser | null>({
  key: 'myApp.login.loginUserAtom',
  default: null
})

Jotaiのatomを作る時keyは要りません。シンプル!

import {atom} from 'jotai'

// Jotaiのatom宣言
const loginUserAtom = atom<LoginUser | null>(null)

関数中でだけ参照されるatomも作れる

Jotaiのatomは関数中でも宣言できます。なので、「atomを内包したatomを作る関数」なんてものも作れます。

const makeLoginUserAtom = (accessToken:string) => {
  // 値の格納場所
  const storeAtom = atom(null)

  // storeAtomが空ならAPI呼ぶけど外からセットもできる
  const interfaceAtom = atom(
    async (get):Promise => {
      return get(storeAtom) || loginUserApi.load(accessToken)
    }, 
    async (get, set, newValue:LoginUser) => {
      set(storeAtom, newValue)
    }
  )
  return interfaceAtom
}

const loginUserAtom = makeLoginUserAtom(localStorage.get('access-token'))

上のコードでmakeLoginUserAtomを何度も呼んでatomを複数作っても問題ありません。Recoilだと同じkeyのatomを複数作ると実行時に怒られますし、関数の中でatom宣言をしてるとESLintに怒られます。

Jotaiはビジネスロジックに合わせた特殊なatomを自分で作るのが大変はかどります。これがRecoilからJotaiに乗り換えた理由の1番です。

メモリリークしづらい

JotaiのatomはReactのコンポーネントツリー中にあるWeakHashMapで値を保持しているので、コンポーネントツリーから外れたらそのうち勝手にメモリ領域が解放されます。RecoilはきちんとしたMapでkeyとvalueの組を管理してるので、プログラマーがRecoilの挙動を深く理解せずちょっとひねったものを書くとメモリリークにつながりやすいです。

テストなどで初期値を入れるのが便利

Recoilではatomに外から初期値を入れるにはRecoil Syncを使ってかなり色々書く必要があります。(参考:Recoil Syncでさらに快適フロントエンド開発

JotaiではStoreというオブジェクトにstore.set(***, ***)して、Providerに渡すだけで初期値を与えられます。

これはテストやStorybookでモックデータをセットしたり、簡易的なDIとして利用するのに大変便利です。

import {createStore, Store, Provider} from 'jotai'

// 値のストアを一つ作っておく
const store: Store = createStore()
// 任意のatomの初期値としてモックデータを入れる
store.set(loginUserAtom, {
  id: 1,
  name: "Mock User"
})

// testing-library/reactで使うcomponentのWrapper
const TestWrapper:React.FC<React.PropsWithChildren> = ({children}) => {
  // Providerにstoreを渡す
  return <Provider store={store}>{children}</Provider>
}

これがRecoilからJotaiに乗り換えた理由の2番です。

TypeScript製

JotaiはTypeScriptで使うことが前提なので、TypeScriptで使いやすいように型がかなり細かく定義されています。React.useReducerのようなインターフェイスを持つatomを作るための便利関数を作ってどんどん育てていったのですが、Jotaiの型定義のおかげでかなり安全に使えてプログラミングしやすい関数になりました。

さいごに

開発効率に大きく影響するような箇所は、できるだけ楽に便利に使えるライブラリを選択したいものですね。しかしアプリケーションが大きく育つにつれてライブラリを乗り換えるのが大変になるので、「とりあえずユーザーの多いライブラリを使っておこう」という気持ちになるのもわかります。

そこで、僕がフロントエンドの経験豊富な同僚から言われた言葉をご紹介します。

「どれを使ったって、5年後には無くなるか別のものがデファクトになってるよ」

もう20年以上もプログラマーやってますが、ほんとそうだなあと思います。気楽に選んで試して楽しみましょう。

開発メンバー募集中

より良いチームワークを生み出す

チームの創造力を高めるコラボレーションツール

製品をみる