Recoil 0.0.9 と 0.0.10 は、いくつかのバグ修正、TypeScript のサポート、そしてグローバルな Recoil アトムの状態を観察、検査、管理するための新しい API である Recoil スナップショット を搭載してリリースされます。 これを実現するのを手伝ってくれたすべての人に改めて感謝します。今後のエキサイティングな開発にご期待ください!
バグ修正
- サーバーサイドレンダリングの修正。ただし、まだ公式にはサポートしていません。(#233, #220, #284) - @fyber-LJX、@Chrischuck、@aulneau に感謝します
- 場合によってはセレクターが依存関係のサブスクリプションを記録するのを修正 (#296) - @drarmstr に感謝します
useRecoilCallback()
内のアップデーターが現在の状態を取得するのを修正 (#260) - @drarmstr に感謝します- オープンソースビルドで特定のエラーをスローしたときのエラーメッセージを修正。 (#199) - @jonthomp に感謝します
- オープンソースビルドの Flow エラーを削減 (#308) - @Komalov に感謝します
改善
- ユーザーがほとんどの Recoil フックでアトムまたはセレクターを使用しない場合、意味のあるメッセージを含むエラーをスローする (#205) - @alexandrzavalii に感謝します
- テストの改善 (#321, #318, #294, #262, #295) - @aaronabramov、@Komalov、@mondaychen、@drarmstr、@tyler-mitchell に感謝します
- オープンソースビルドの改善 (#249, #203, #33) - @tony-go、@acutmore、@jaredpalmer に感謝します
TypeScript のサポート
TypeScript のサポートは、API との同期をより良く保つために、DefinitelyTyped
ではなく Recoil GitHub リポジトリにロールインされています。 (#292 & #339) - @csantos42 に感謝します
Recoil スナップショット
#312, #311, #310, #309, #260, #259, #258, #257, #256 - @drarmstr とチームの皆さんに感謝します
Recoil に Snapshot
の概念を導入しています。 Snapshot
は、Recoil アトムの状態の不変スナップショットです。これは、グローバルな Recoil の状態と派生状態を観察、検査、管理するための API を標準化することを目的としています。開発ツール、グローバル状態の同期、履歴、ナビゲーションに役立ちます。
API
スナップショットの読み取り
Snapshot
クラスは、個々の Recoil アトムとセレクターの値を取得するための以下のメソッドを公開しています
class Snapshot {
getLoadable: <T>(RecoilValue<T>) => Loadable<T>;
getPromise: <T>(RecoilValue<T>) => Promise<T>;
...
}
スナップショットは、アトムの状態に関して読み取り専用です。アトムの状態を読み取り、セレクターの派生状態を評価するために使用できます。非同期セレクターの場合、getPromise()
メソッドを使用して評価された値を待つことができるため、静的アトムの状態に基づいてセレクターの値がどうなるかを確認できます。
スナップショットの変換
スナップショットを変更したい場合があります。スナップショットは不変ですが、一連の変換を使用して自身を新しい不変スナップショットにマッピングするメソッドがあります。マップメソッドは、MutableSnapshot
を渡されるコールバックを受け取ります。これはコールバック全体で変更され、最終的にマッピング操作によって返される新しいスナップショットになります。
class Snapshot {
...
map: (MutableSnapshot => void) => Snapshot;
asyncMap: (MutableSnapshot => Promise<void>) => Promise<Snapshot>;
}
class MutableSnapshot {
set: <T>(RecoilState<T>, T | DefaultValue | (T => T | DefaultValue)) => void;
reset: <T>(RecoilState<T>) => void;
}
set()
と reset()
は、書き込み可能なセレクターの set()
関数に提供されるコールバックと同じシグネチャを持っていることに注意してください。
例
const newSnapshot = snapshot.map(({set}) => set(myAtom, 42));
フック
Recoil には、スナップショットを操作するための以下のフックがあります
useRecoilSnapshot()
- スナップショットへの同期アクセスuseRecoilCallback()
- スナップショットへの非同期アクセスuseRecoilTransactionObserver()
- すべての状態更新のスナップショットを購読するuseGotoRecoilSnapshot()
- 現在の状態をスナップショットと一致するように更新する
useRecoilSnapshot()
function useRecoilSnapshot(): Snapshot
このフックを使用すると、コンポーネントのレンダリング中に現在の状態のスナップショットを同期的に取得できます。概念的には単純ですが、このフックはそれを使用するコンポーネントを Recoil の状態変更にサブスクライブするため、常に現在の状態のスナップショットでレンダリングされます。したがって、このフックの使用には注意してください。使用したい場合の1つの例は、最初のレンダリングで状態を同期的に取得する必要があるサーバーサイドレンダリングのサポートです。将来的には、パフォーマンスのためにデバウンスする機能を提供する可能性があります。
例
変更が適用された現在の状態に基づいて href
を持つ <a>
アンカーをレンダリングする <LinkToNewState>
コンポーネントを定義します。この例では、uriFromSnapshot()
は、ページの読み込み時に復元できる URI に現在の状態をエンコードするユーザー定義関数です。
function LinkToNewState() {
const snapshot = useRecoilSnapshot();
const newSnapshot = snapshot.map(({set}) => set(myAtom, 42));
return <a href={uriFromSnapshot(newSnapshot)}>Click Me!</a>;
}
これは簡略化された例です。近日公開予定のブラウザ履歴永続化ライブラリには、より拡張性が高く最適化されたリンクを生成するためのこのようなヘルパーがあります。たとえば、ブラウザ履歴を経由することなくローカル状態を更新するために、クリックハンドラーをハイジャックします。
useRecoilCallback()
type CallbackInterface = {
snapshot: Snapshot,
gotoSnapshot: Snapshot => void,
set: <T>(RecoilState<T>, (T => T) | T) => void,
reset: <T>(RecoilState<T>) => void,
};
function useRecoilCallback<Args, Return>(
callback: CallbackInterface => (...Args) => ReturnValue,
deps?: $ReadOnlyArray<mixed>,
): (...Args) => ReturnValue
useRecoilCallback()
フックは、コールバック関数を生成するための React useCallback()
フックに似ています。ただし、入力コールバック関数を提供するだけでなく、Snapshot
と set()
/reset()
コールバックへのアクセスを提供するコールバックインターフェースパラメータを提供する関数でラップして、現在のグローバル状態を更新します。提供される Snapshot
は、コールバック関数が最初に作成されたときではなく、コールバックが呼び出されたときの状態を表します。
注:これは API のわずかな破壊的変更ですが、Recoil のバージョン 0.0.x
であり、まだセマンティックバージョニングを完全に開始していません。
useRecoilCallback()
は、メモ化を制御するためのオプションの deps
配列パラメータも受け取ります。 react-hooks/exhaustive-deps
lint ルールを拡張して、これが適切に使用されていることを確認できます。
useRecoilCallback()
を使用する動機
アトムまたはセレクターが更新された場合に React コンポーネントを再レンダリングするためにサブスクライブすることなく、Recoil 状態を非同期的に使用します。
レンダリング時に行いたくない高価なルックアップを非同期アクションに延期します。
Recoil 状態の読み取りまたは書き込みも行いたい副作用を実行します。
どのアトムまたはセレクターを更新したいかをレンダリング時にわからない可能性があるため、
useSetRecoilState()
を使用できないアトムまたはセレクターを動的に更新します。レンダリング前のプリフェッチ
例
クリックされたときに高価なセレクターを評価するボタンコンポーネント。
function ShowDetailsButton() {
const onClick = useRecoilCallback(({snapshot}) => async () => {
const data = await snapshot.getPromise(expensiveQuery);
showPopup(data);
});
return <button onClick={onClick}>Show Details</button>;
}
useRecoilTransactionObserver()
function useRecoilTransactionObserver_UNSTABLE(({
snapshot: Snapshot,
previousSnapshot: Snapshot,
}) => void)
このフックは、Recoil アトムの状態が変更されるたびに実行されるコールバックをサブスクライブします。複数の更新は、単一のトランザクションでまとめてバッチ処理される場合があります。このフックは、状態の変更の永続化、開発ツール、履歴の構築などに最適です。将来的には、特定の条件にサブスクライブしたり、パフォーマンスのためにデバウンスしたりする機能を許可する可能性があります。
デバッグオブザーバーの例
function DebugObserver() {
useRecoilTransactionObserver_UNSTABLE(({snapshot}) => {
window.myDebugState = {
a: snapshot.getLoadable(atomA).contents,
b: snapshot.getLoadable(atomB).contents,
};
});
return null;
}
useGotoRecoilState()
function useGotoRecoilSnapshot(): Snapshot => void
このフックは、パラメータとして Snapshot
を受け取り、現在の <RecoilRoot>
状態をこのアトムの状態と一致するように更新するコールバックを返します。
タイムトラベルの例
以前の状態に戻って復元する機能を備えた状態変更の履歴のリストの例。
function TimeTravelObserver() {
const [snapshots, setSnapshots] = useState([]);
useRecoilTransactionObserver_UNSTABLE(({snapshot}) => {
setSnapshots([...snapshots, snapshot]);
});
const gotoSnapshot = useGotoRecoilSnapshot();
return (
<ol>
{snapshots.map((snapshot, i) => (
<li key={i}>
Snapshot {i}
<button onClick={() => gotoSnapshot(snapshot)}>
Restore
</button>
</li>
)}
</ol>
);
}
状態の初期化
<RecoilRoot>
コンポーネントには、アトムの状態を初期化するために使用できる initializeState
prop もあります。この prop は、初期アトム状態の設定に使用できる MutableSnapshot
パラメータを持つ関数を受け取ります。これは、すべてのアトムを事前に知っている場合に永続化された状態を読み込むのに役立ちます。最初のレンダリングのために状態を同期的に設定する必要があるサーバーサイドレンダリングに役立ちます。
例
function MyApp() {
return (
<RecoilRoot
initializeState={({set}) => {
for (const [atom, value] of atoms) {
set(atom, value);
}
}}
>
<AppContents />
</RecoilRoot>
);
}
次は何ですか?
スナップショットを使用すると、グローバル状態を観察し同期できます。しかし、個々のアトムを扱うための、よりきめ細かく構成可能なシステムが必要な場合はどうでしょうか。アトムレベルで副作用を観察し、対処するための*アトム効果*の概念に取り組んでいます。これにより、状態の永続化や可変ストレージとの双方向同期が容易になります。ブラウザの URI 履歴、ブラウザのローカルストレージ、RESTful API などとの状態の同期を考えてみてください。近日公開!
ここで紹介した Snapshot
API を使用すると、個々のアトムとセレクターの現在の状態を検査できます。利用可能なノードのセットを検査し、データフローグラフ構造を探索できるように API を拡張します。これは、開発ツールを構築するのに強力です。乞うご期待!
そしてもちろん、React Concurrent Mode のエキサイティングなサポートと、速度、スケーラビリティ、メモリ管理の改善に取り組んでいます。