こんにちは、サイオステクノロジーの遠藤です。
Reactでアプリを作っていると、「状態管理ってどうすればいいの?」と悩むことがありますよね。たとえば、useState
を利用していると、コンポーネントの階層が深くなってきたときにpropsのバケツリレーで複雑になりうまく管理できなくなることがあります。
そんなときに便利なのが Jotai というライブラリです! Jotaiを使うと、シンプルな書き方で状態を管理でき、複雑なアプリでも扱いやすくなります。
この記事では、Jotaiの基本的な使い方を初心者向けにわかりやすく解説します。「Reactの状態管理をもっと簡単にしたい!」という方は、ぜひ最後まで読んでみてください!
Jotaiとは?
Jotaiは、Reactの状態管理をシンプルにするライブラリです。「Atom(アトム)」と呼ばれる単位で状態を管理し、それらを組み合わせることでアプリの状態を作ります。
Jotaiの特徴は、必要な部分だけを効率よく更新すること。ReactのContextを使った状態管理では、不要な再レンダリングが発生しがちですが、Jotaiは依存するAtomだけを更新するため、パフォーマンスが最適化されます。そのため、メモ化(memoization)を意識する必要が少なく、スムーズに開発できます。
また、小規模なアプリから大規模なTypeScriptアプリまで対応でき、公式のユーティリティや拡張機能も豊富に用意されています。シンプルなuseState
の代わりに使うことも、大規模なプロジェクトで本格的に活用することもできる柔軟なライブラリです。
公式サイト : https://jotai.org/
Core API
JotaiのAPIはとてもシンプルで、必要最小限の機能だけが提供されています。基本的には「atom」「useAtom」「Store」「Provider」という4つのCore APIで状態管理が完結します。
atom
Jotaiのatomは、アプリの状態を定義するための基本的な単位です。Reactの useState
に似ていますが、グローバルに管理できるのが特徴です。
atomは「状態の設定(atom config)」を作るだけで、実際に値を保持しているわけではありません。値はStoreと呼ばれる仕組みに保存されます。そのため、atom自体は変更できず、常に不変(immutable)なオブジェクトとして扱われます。
atomの作成方法
基本的なatomは atom
関数を使って作成します。初期値を渡せば、その値を持つatomが作成されます。
1 2 3 4 5 | import { atom } from 'jotai' const priceAtom = atom(10) // 数値の状態 const messageAtom = atom('hello') // 文字列の状態 const productAtom = atom({ id: 12, name: 'good stuff' }) // オブジェクトの状態 |
useAtom
useAtom
は、atomの値を読み取ったり更新したりするためのフック です。Reactの useState
に似た使い方ができ、atomの値と更新関数を返します。
1 | const [value, setValue] = useAtom(anAtom) |
この value
は現在のatomの値で、setValue
を使って新しい値に更新できます。
useAtomの基本的な使い方
まず、atomを作成します。
1 2 3 | import { atom, useAtom } from 'jotai' const countAtom = atom(0) // 初期値 0 の atom |
次に、コンポーネント内で useAtom
を使って、この countAtom
の値を取得・更新します。
1 2 3 4 5 6 7 8 9 10 | const Counter = () => { const [count, setCount] = useAtom(countAtom) return ( <div> <p>現在のカウント: {count}</p> <button onClick={() => setCount(count + 1)}>+1</button> </div> ) } |
このように、 useAtom
を使えば、コンポーネント内で簡単にグローバルな状態を管理できます。
注意点: useAtom内でatomを直接作らない
useAtom(atom(0))
のように、コンポーネント内で毎回新しいatomを作ると、レンダリングのたびに異なるatomが作られてしまい、無限ループが発生する ことがあります。
1 | const [count] = useAtom(atom(0)) // ❌ 毎回新しいatomを作るのでNG |
代わりに、コンポーネントの外でatomを定義する ようにしましょう。
1 2 | const countAtom = atom(0) // ✅ 外で定義すればOK const [count] = = useAtom(doubleCountAtom) // ✅ 正しく動作 |
Store
Store
は、atomの値を管理するための独立した状態コンテナです。通常、Jotaiはデフォルトの Store
を使用するため、特に意識しなくても状態を管理できます。しかし、createStore
を使ってカスタム Store
を作成すると、複数の状態ツリーを独立して管理 できるようになります。
Storeの作成と使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | import { atom, createStore } from 'jotai' const myStore = createStore() // 新しいStoreを作成 const countAtom = atom(0) // Storeを直接操作 myStore.set(countAtom, 1) // countAtom の値を 1 に更新 console.log(myStore.get(countAtom)) // 1 // 値の変更を監視 const unsubscribe = myStore.sub(countAtom, () => { console.log( 'countAtomが変更されました:' , myStore.get(countAtom)) }) |
Providerとは?
Provider
は、JotaiのStoreを特定のコンポーネントツリーに適用するためのコンポーネント です。
通常、Jotaiはデフォルトの Store
を使うので Provider
なしでも動作しますが、以下のような場合に Provider
を使うと便利です。
Providerを使うメリット
- 異なる状態を持つ複数のコンポーネントツリーを作れる
- 例えば、2つの
Provider
を使うことで、同じatom
を異なる状態として管理できます。
- 例えば、2つの
- 初期値を適用できる
Provider
のstore
に初期値を設定して、コンポーネントごとに異なるデータを扱えます。
Provider
の再マウントで状態をリセットできるProvider
を再マウントすると、その配下のatomの状態をクリアできます。
Providerの使い方
デフォルトのProvider(特に指定しない場合)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | import { atom, useAtom, Provider } from 'jotai' const countAtom = atom(0) const Counter = () => { const [count, setCount] = useAtom(countAtom) return ( <div> <p>カウント: {count}</p> <button onClick={() => setCount(count + 1)}>+1</button> </div> ) } const App = () => ( <Provider> <Counter /> </Provider> ) |
この場合、Provider
を使わなくてもデフォルトのStoreが適用されます。
カスタムStoreをProviderに適用
作成した Store
を Provider
に適用すると、独立した状態管理 が可能になります。
1 2 3 4 5 6 7 | const myStore = createStore() const Root = () => ( <Provider store={myStore}> <App /> </Provider> ) |
こうすることで、myStore
を使用した状態管理が App
以下のコンポーネントに適用されます。
異なるProviderで独立した状態を持たせる
異なる Provider
を使うと、同じ atom
でも別々の状態として扱えます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | const Counter = () => { const [count, setCount] = useAtom(countAtom) return ( <div> <p>カウント: {count}</p> <button onClick={() => setCount(count + 1)}>+1</button> </div> ) } const App = () => ( <div> <Provider> <h2>Provider 1</h2> <Counter /> </Provider> <Provider> <h2>Provider 2</h2> <Counter /> </Provider> </div> ) |
この例では、それぞれの Provider
が独立した Store
を持っているため、1つのカウンターを更新してももう1つには影響しません。
Tips : Jotaiで状態をローカルストレージに保存する方法
CoreAPIの機能ではありませんが、、atomWithStorage
を使うことで、状態を localStorage
や sessionStorage
に簡単に保存できます。これにより、ユーザーの設定やデータを次回のセッションでも保持できます。
atomWithStorage
の基本
atomWithStorage
は Jotai の jotai/utils
モジュールに含まれており、指定したキーで localStorage
または sessionStorage
と同期されます。ページの再読み込み後も、保存された値が自動的に取得されます。
例: テーマの状態を保存する
以下のコードでは、atomWithStorage
を使ってテーマ(dark
or light
)を localStorage
に保存し、ページをリロードしても選択したテーマが保持されるようにしています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | import { useAtom } from 'jotai' ; import { atomWithStorage } from 'jotai/utils' ; // 'dark' というキーでローカルストレージに保存するAtom const theme = atomWithStorage( 'dark' , false ); export default function Page() { const [appTheme, setAppTheme] = useAtom(theme); const handleClick = () => setAppTheme(!appTheme); return ( <div className={appTheme ? 'dark' : 'light' }> <h1>テーマ切り替え</h1> <button onClick={handleClick}> {appTheme ? 'DARK' : 'LIGHT' } </button> </div> ); } |
まとめ
本記事では、Jotaiの基本的な使い方 や 便利な機能 について解説しました。Jotaiは useState
のように直感的に使え、シンプルな記述でグローバルな状態管理ができる のが特徴です。
また、createStore()
や Provider
を活用することで、画面ごとに異なる状態を管理したり、状態をリセットしたりすることも可能です。Reduxのような複雑なセットアップが不要で、初心者でも扱いやすいライブラリ なので、「もっと簡単に状態を管理したい!」という方におすすめです。Jotaiを使って、Reactアプリの状態管理をシンプルにしてみましょう! 🎉
ではまた~