こんにちは。
開発本部 SJC共同開発推進室の中野です。
ReactでWebフロントエンドの開発を行う際、多くの開発者が「状態管理ライブラリ」について一度は頭を抱える思います。
ReduxやZustandそして本記事で取り扱うRecoilやJotaiのような多種多様な状態管理ライブラリが存在しますが、「これが最適解!」と断言できるものは存在しません。
そのため、ライブラリ選定で多くの開発者が混乱することでしょう。
(私自身もかなり迷いました…
そこで今回は、現在担当しているプロジェクトで大活躍中の状態管理ライブラリ、Jotaiについて
「なぜJotaiを採用したのか」というバックグラウンドに焦点を当て、
RecoilとJotaiで迷っている方、あるいは「そもそもJotaiって何?」という方に向けて、Jotaiの概要とその魅力をご紹介します。
なぜ状態管理ライブラリが必要なのか
そもそも「なぜ状態管理ライブラリが必要なの?」
と疑問に思う方もいるかと思いますので嚙み砕いて解説しますね。
Webのフロントエンド開発において、何かの状態そのものや、その状態に紐づく情報を、画面をまたいで持っておきたいということがよくあります。
- ユーザーの認証情報
- サーバーのレスポンス
- キャッシュされたデータ
- 永続化されていないローカルデータ
- UIの状態
こうしたものを、React等のフロントエンドフレームワークではState(状態)と呼んでいます。
各コンポーネントでこれらの状態を個別に管理すると、コードの複雑性が増し、データの一貫性を保つことが難しくなります。さらに、アプリケーションが大規模になるにつれてこの状態管理のコストが増えるため、シンプルさを目指していたJavaScriptのSPAが逆に煩雑化してしまいます。
こうした理由で、私はReactでの状態管理は実装難易度の高いテーマの一つだと感じています。。。
ここで状態管理ライブラリが大活躍!
複雑なアプリケーションでもデータの一貫性を保ちながら効率的に開発を進めることが可能になります。適切な状態管理は、スケーラブルでメンテナンスしやすいコードベースの構築に不可欠なのです。
Storeベース vs Atomベース:状態管理ライブラリの比較
状態管理ライブラリには大きく分けて「Storeベース」と「Atomベース」の二つのアプローチが存在します。
Storeベースは、アプリケーション全体の状態を一元的に管理するための中央ストアを持つのに対し、Atomベースは、複数の小さな状態単位(Atom)を持ち、それぞれのAtomが独立して管理されます。
項目 | Storeベース | Atomベース |
---|---|---|
学習コスト | 中程度 | 低い |
機能の複雑さ | 高い | 低い |
表現力 | 高い | 高い |
コード量 | 多い | 少ない |
ドキュメント量 | 多い | 少ない |
Storeベースでは、アプリケーション全体の状態を一元的に管理するため明確なデータフローが求められます。これは大規模なアプリケーションで役立ちますが、学習コストやコードの複雑さが比較的高くなります。
一方、Atomベースでは、個々の状態単位が独立して管理されるため、それぞれの状態を簡単に管理できます。独立した状態管理が必要な場合や、パフォーマンスの最適化が重要な場合に役立ちます。Atomベースは、学習コストやコードの複雑さが低く、小規模なプロジェクトや単純な状態管理に適しています。
状態管理ライブラリの選定
npm trends のデータを見ると、
Reactの状態管理ライブラリはRedux一強なんです。
ではなぜReduxを採用しなかったのかというと、複雑だったから。
「これだけ根強い人気があるなら、Reduxで良いのでは」と思っていましたが、
調査を進め、試験実装してみるとかなり複雑なことに気が付きました。
理解しづらいところがあり、学習コストがかかるのでStoreベースの状態管理ライブラリはやめよう…
と、同じく StoreベースのZustandをスルーして、AtomベースのJotaiを採用することにしました。
Jotaiを採用したワケ
実は、当初はJotaiではなく、Meta(旧FaceBook)社が2020年に発表した「Recoil」を採用しようとしていました。
Reduxに比べて構造が単純になっており、「扱いやすくていいじゃん~」と調査および試験実装を進めていたのですが、
Recoilは諸事情で2022年から開発が止まっているとのことで、配布元で繰り返し勧められていたこともありJotaiに乗り換えました。
Jotaiとは
Jotaiの特徴をざっくりまとめるとこんな感じ。
- 日本語の「状態」から名付けられた
- Recoilにインスパイアされ、Atomモデルを採用
- Atomの依存関係に基づいてレンダリングが最適化されるため余分な再レンダリング問題を解決、メモ化技術の必要性を排除
- ミニマルなAPIを提供
- TypeScript指向
「Recoilにインスパイアされた」とあるように、
Recoilの良いところをそのままに、不便なところが無くなった上位互換(?)のようなイメージです。
しかも作者が日本人で日本語の情報が豊富。
公式ドキュメントも詳細に記載されているので開発を進めやすいです!
Jotaiの魅力①
Jotaiの最大の魅力と言っても過言ではないのですが、とにかくシンプル。
Reactが提供する代表的なフックであるuseStateに非常に近い感覚でステートを扱えるので、ミニマムにコードが書けます。
開発者としてはシンプルで直感的な方が嬉しいですよね。
以下のサンプルコードを見ていただければ、その使いやすさがわかるでしょう。
1 2 3 4 |
// state.ts import { atom } from 'jotai' export const state = atom<string>("Hello World"); |
1 2 3 4 5 6 7 8 9 10 11 |
// App.tsx import { useAtom } from "jotai"; import { state } from "./state"; funstion App () { const [exampleState, setExampleState] = useAtom(state); return <>{exampleState}</>; // 画面には "Hello World" が表示されます }; export default App; |
Jotaiの魅力②
二つ目の魅力は何と言っても導入コストが低いことです。
魅力①でシンプルさについて触れましたが
useStateを使用する代わりにuseAtomを使用するだけで導入できます。
分かりやすいですよね?
たったこれだけでアプリケーションの状態管理をJotaiに移行することが可能になります。
もちろん他にもJotaiの機能がありますが、
Reactが提供する機能と似ている部分が多く、Jotai全体が一貫した使いやすさを提供しているように感じました。
Jotaiの魅力➂
まずはこちらのサンプルコードを見てください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// 親から子へ… function ParentComponent() { const [data, setData] = useState('Sample Data'); return <ChildComponent data={data} />; } // 子から孫へ… function ChildComponent(props) { return <GrandChildComponent data={props.data} />; } // 孫からひ孫へ… function GrandChildComponent(props) { return <GreatGrandChildComponent data={props.data} />; } // ひ孫でデータを表示 function GreatGrandChildComponent(props) { return <h1>{props.data}</h1>; // 画面には "Sample Data" が表示されます } |
美しくない。。。data={…} が踊っていて見苦しいです。
Reactのアプリケーション開発が大規模になるほど親コンポーネントから子コンポーネントへデータを渡す「propsのバケツリレー」が増え、可読性が低下しがちです。
このようなコードでは、データを必要とする全てのコンポーネントにpropsを渡さなければならず、コンポーネント間の依存関係が増え、再レンダリングが多発。これにより、アプリケーションのパフォーマンスも低下する可能性があります。
そこで!Jotaiを利用してこのように変えると。。。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
import React from 'react'; import { atom, useAtom } from 'jotai'; // 状態を定義 const dataAtom = atom('Sample Data'); // 親コンポーネント function ParentComponent() { return <ChildComponent />; } // 子コンポーネント function ChildComponent() { return <GrandChildComponent />; } // 孫コンポーネント function GrandChildComponent() { return <GreatGrandChildComponent />; } // ひ孫コンポーネント function GreatGrandChildComponent() { const [data] = useAtom(dataAtom); return <div>{data}</div>; } |
見やすくなりましたね!
必要なコンポーネントのみが状態の変更を監視するため、不要な再レンダリングも減り、パフォーマンスが向上します。
まとめ
状態管理ライブラリ「Jotai」を採用した経緯についてまとめてみました。
無駄な再レンダリングが減り、コードの可読性がかなり上がったので採用して良かったです!
シンプルに実装できるので、後からプロジェクトに合流するメンバーの学習コストも低くおすすめです。
1年間で増加したGitHubのスター数によるランキングがまとめられているJavaScript Rising Starsを見てみると、StoreベースのZustandにはまだ負けていますが、Jotaiが伸びていることがわかりますね!
そんなわけで勝手ながら私はJotaiを応援していきます!