Open hysryt opened 2 years ago
Recoilにインスパイアされている。 全てのステートはグローバルにアクセスできる。 AtomをlocalStrageに保存するのも簡単。
Recoilとの違い
npx create-react-app my-app
cd my-app
npm install jotai
import { atom, useAtom } from 'jotai';
const countAtom = atom(0);
const App = () => {
const [count, setCount] = useAtom(countAtom);
return (
<p>{count}</p>
);
};
const countAtom = atom(0);
const doubleCountAtom = atom((get) => get(countAtom) * 2);
const App = () => {
const [count, setCount] = useAtom(doubleCountAtom);
return (
<p>{count}</p>
);
}
const count1 = atom(1)
const count2 = atom(2)
const count3 = atom(3)
const sum = atom((get) => get(count1) + get(count2) + get(count3))
Jotaiは、Reactの余分な再レンダリングの問題を解決するために生まれました。余分な再レンダリングとは、同じUI結果を生成するレンダリング処理で、ユーザーには何の違いも見えません。
Reactのコンテキスト(useContext + useState)で素朴にこの問題に取り組もうとすると、おそらく多くのコンテキストが必要になり、いくつかの問題に直面することになるでしょう。
従来、これに対するトップダウン的な解決策として、セレクタ・インターフェースを用いる方法がありました。use-context-selectorライブラリはその一例です。この方法の問題点は、セレクタ関数が再レンダリングを防ぐために参照的に等しい値を返す必要があり、多くの場合、何らかのメモ化技術が必要になることです。
Jotaiは、Recoilにインスパイアされたアトミックモデルによるボトムアップアプローチを採用しています。アトムを組み合わせて状態を構築し、アトムの依存性に基づいてレンダリングが最適化されます。これにより、メモ化技術を必要としません。
Jotaiには2つの理念があります。
JotaiのコアAPIはミニマルであり、これをベースに様々なユーティリティを構築することが可能です。
他のライブラリとの違いを見るには、比較ドキュメントをご覧ください。
Jotaiにおける状態は、atomの集合体です。atomは状態の一部分です。ReactのuseStateとは異なり、atomは特定のコンポーネントに縛られることはありません。では、atomの定義と使い方をみていきましょう。
atomというエクスポートされた関数がありますが、これはatom configを作成するためのものです。これは単なる定義で、値を保持しないので「config」と呼んでいます。文脈的に明確な場合は、単に「atom」と呼ぶこともあります。 プリミティブなatom(config)を作るには、初期値を渡すだけです。
import { atom } from 'jotai'
const priceAtom = atom(10)
const messageAtom = atom('hello')
const productAtom = atom({ id: 12, name: 'good stuff' })
また、導出atomを作ることも可能です。3つのパターンを用意しています。
導出atomを作成するには、読み込み関数とオプションの書き込み関数を渡します。
const readOnlyAtom = atom((get) => get(priceAtom) * 2)
const writeOnlyAtom = atom(
null, // 第一引数にはnullを渡す
(get, set, update) => {
// updateは、このatomを更新するために受け取る任意の値
set(priceAtom, get(priceAtom) - update.discount)
}
)
const readWriteAtom = atom(
(get) => get(priceAtom) * 2,
(get, set, newPrice) => {
set(priceAtom, newPrice / 2)
// 同時にいくつでもアトムを設定することができる
}
)
読み込み関数のget
はatomを読み取るものです。リアクティブであり、依存関係はトラッキングされます。
書き込み関数の get
もatom値を読み込むものですが、トラッキングはされません。さらに、未解決の非同期値は読めません。非同期の動作については、asyncのドキュメントを参照してください。
書き込み関数の set
は、atomの値を書き込むためのものです。対象atomの書き込み関数が呼び出されます。
atom configはどこでも作成可能ですが、参照一致が重要です。また、動的に作成することもできます。render関数でatomを作成するには、安定した参照を得るために useMemo または useRef が必要です。useMemoかuseRefを使うか迷ったら、useMemoを使いましょう。
const Component = ({ value }) => {
const valueAtom = useMemo(() => atom({ value }), [value])
// ...
}
useAtomフックは、状態にあるatom値を読み込むためのものです。状態は、atom configとatom値のWeakMapとして見ることができます。
useAtom関数は、ReactのuseStateと同様に、atom値と更新関数をタプルとして返します。引数としてatom()で作成したatom configを受け取ります。
初期状態では、状態には値が格納されていません。useAtomによりatomが初めて使用されたとき、初期値がステートに格納されます。atomが導出atomの場合は、読み込み関数が実行され、初期値が計算されます。atomが使用されなくなったとき、つまりatomを使用しているコンポーネントがすべてアンマウントされ、atom構成が存在しなくなったとき、状態の値はガベージコレクションされます。
const [value, updateValue] = useAtom(anAtom)
updateValueは引数を1つだけ取り、atomのwrite関数の第3引数に渡されます。動作は書き込み関数がどのように実装されるかに完全に依存します。
Providerとは、コンポーネントのサブツリーに対して状態を提供するものです。複数のサブツリーに対して複数のProviderを使用することができ、入れ子にすることも可能です。これは、通常のReact Contextと同じように動作します。
Providerが存在しないツリーでatomが使用された場合、デフォルトの状態が使用されます。これはいわゆるProvider-lessモードです。
プロバイダーがいくつかの点で便利です。
const SubTree = () => (
<Provider>
<Child />
</Provider>
)
Jotaiでは非同期サポートは第一級です。React Suspenseをフルに活用しています。
技術的には、React.lazy以外のSuspenseの使い方はReact 17ではまだ未サポート/未ドキュメントの状態です。もしブロックされているようなら、guides/no-suspenseをチェックしてみてください。
非同期atomを使用するには、コンポーネントツリーを
const App = () => (
<Provider>
<Suspense fallback="Loading...">
<Layout />
</Suspense>
</Provider>
)
コンポーネントツリーでより多くの
atomの読み込み関数は、Promiseを返すことができます。Promiseが達成されると、一時停止して再レンダリングします。
最も重要なことは、useAtomは解決された値しか返さないということです。
const countAtom = atom(1)
const asyncCountAtom = atom(async (get) => get(countAtom) * 2)
// 読み込み関数はPromiseを返す
const Component = () => {
const [num] = useAtom(asyncCountAtom)
// num は数値であることが保証されている
}
atomは、読み取り関数が非同期であるだけでなく、その依存関係の1つ以上が非同期である場合に非同期となります。
const anotherAtom = atom((get) => get(asyncCountAtom) / 2)
// このatomはPromiseを返さないが、
// `asyncCountAtom`が非同期なので、非同期読み出しatomとなる。
Jotaiは日本語で「状態」を意味します。Zustandはドイツ語で「状態」を意味します。
JotaiはRecoilに近い。ZustandはReduxに近い。
JotaiのステートはReactのコンポーネントツリーの中にあります。ZustandのステートはReactの外側のストアにあります。
Jotaiのステートはatomからなる(ボトムアップ)。Zustandのステートは1つのオブジェクト(つまりトップダウン)。
大きな違いは、ステートモデルです。Zustandは基本的に1つのストアです(複数のストアを作ることもできますが、分離されています)。Jotaiはプリミティブなアトムで、それを合成します。その意味では、プログラミングのメンタルモデルの問題ですね。 JotaiはuseState+useContextの置き換えと見ることができる。複数のコンテキストを作る代わりに、アトムは一つの大きなコンテキストを共有します。 Zustandは外部のストアであり、フックは外部の世界とReactの世界を繋ぐためのものです。
(免責事項:筆者はRecoilにあまり詳しくありません。偏りがあり、正確でない可能性があります)。
useUpdateAtom
を useSetAtom
にリネームし、 jotai/util からコアに移動https://jotai.org/docs/utils/use-update-atom
useAtom
が値とセット関数を取得するのに対し、useSetAtom
はセット関数のみを取得する。
アトムの値に応じて再レンダリングの必要がない場合は useSetAtom
を使用した方が良い。
Recoil の useSetRecoilState
と同等と思われる。
useAtomValue
が jotai/util からコアに移動https://jotai.org/docs/utils/use-atom-value
useAtom
が値とセット関数を取得するのに対し、useAtomValue
はセット関数のみを取得する。
Recoil の useRecoilValue
と同等と思われる。
これによって、Jotaiが公開するAPIは atom
、useAtom
、Provider
、userSetAtom
、useAtom
の5つとなった。
unstable_createStore
を追加Storeを作成する。 StoreはReact以外からも使用ができ、Atomの更新、取得、サブスクリプションが可能。
import { atom, unstable_createStore } from "jotai";
const countAtom = atom(0);
const store = unstable_createStore();
store.sub(countAtom, () => {
const value = store.get(countAtom);
console.log('更新: ' + value);
});
store.set(countAtom, 1); // 更新: 1
store.set(countAtom, c => c + 1); // 更新: 2
https://jotai.org/