メインコンテンツにスキップ

Sync Atom Effect - syncEffect()

syncEffect() は、同期する必要がある atoms をタグ付けし、外部ストアで値を初期化するために使用される atom effect です。唯一必須のオプションは、入力検証のための refine です。itemKey オプションを使用すると、この特定の atom のキーを外部ストアで指定できます。指定しない場合、デフォルトで atom 自身のキーになります。複数の外部ストアがある場合は、同期する外部ストアを照合するために storeKey を指定することもできます。より高度なケースのために、readwrite などの追加オプションもあります。

入力検証

外部システムからの入力を検証し、mixed から強く型付けされた Flow または TypeScript 入力に refine するために、recoil-syncRefine ライブラリを使用します。このライブラリは、型の記述とランタイム検証の実行のために、構成可能な一連の関数を使用します。syncEffect()refine プロパティは、Refine Checker を受け取ります。Refine checker の型は、atom の型と一致する必要があります。

単純な文字列 atom の例

  syncEffect({ refine: string() }),

nullable な数値の例

  syncEffect({ refine: nullable(number()) }),

カスタムユーザー クラス

  syncEffect({ refine: custom(x => x instanceof MyClass ? x : null) }),

より複雑な例

  syncEffect({ refine: object({
id: number(),
friends: array(number()),
positions: dict(tuple(bool(), number())),
})}),

詳細については、Refine のドキュメントを参照してください。

Item と Store のキー

itemKey は、ストアのアイテムを識別するためのユニークなキーを指定します。指定しない場合は、デフォルトで atom のキーになります。カスタムの read() または write() が使用されている場合は、item キーを アップグレードしたり、複数のitem キーを使用したりするために、上書きできます。

storeKey は、同期する外部ストアを指定するために使用できます。対応する <RecoilSync>storeKey と一致する必要があります。これは、アップグレードする場合や、複数のストアがある場合に便利です。

atom({
key: 'AtomKey',
effects: [
syncEffect({
itemKey: 'myItem',
storeKey: 'storeA',
refine: string(),
}),
],
});

Atom Families

atom family の atoms は、syncEffect() で同期することもできます。family 内の個々の atom は、同期する個別のアイテムとして扱われます。デフォルトの item キーには、family パラメータのシリアライズが含まれます。独自の itemKey を指定する場合は、各 atom を一意に識別するために family パラメータもエンコードする必要があります。パラメータは、atom family の effects オプションのコールバックを使用して取得できます。

atomFamily({
key: 'AtomKey',
effects: param => [
syncEffect({
itemKey: `myItem-${param}`,
storeKey: 'storeA',
refine: string(),
}),
],
});

後方互換性

以前のバージョンの状態を持つレガシーシステムまたは外部システムをサポートすることが重要になる場合があります。これにはいくつかのメカニズムを利用できます。

atom 型のアップグレード

atom がストアに永続化されており、その後 atom の型を変更した場合は、Refine の match()asType() を使用して型をアップグレードできます。この例では、現在数値である ID を読み取りますが、以前は文字列またはオブジェクトとして保存されていました。これにより、以前の型がアップグレードされ、atom は常に最新の型を保存します。

const myAtom = atom<number>({
key: 'MyAtom',
default: 0,
effects: [
syncEffect({ refine: match(
number(),
asType(string(), x => parseInt(x)),
asType(object({value: number()}), x => x.value)),
}),
],
});

atom キーのアップグレード

atom のキーも時間とともに変更される可能性があります。read オプションを使用すると、外部ストアから atom を読み取る方法を指定できます。

const myAtom = atom<number>({
key: 'MyAtom',
default: 0,
effects: [
syncEffect({
itemKey: 'new_key',
read: ({read}) => read('new_key') ?? read('old_key'),
}),
],
});

読み取り時のより複雑な変換も可能です。以下を参照してください。

atom ストレージのアップグレード

複数のエフェクトを使用して、新しい外部ストアと同期するように atom を移行することもできます。

const myAtom = atom<number>({
key: 'MyAtom',
default: 0,
effects: [
syncEffect({ storeKey: 'old_store', refine: number() }),
syncEffect({ storeKey: 'new_store', refine: number() }),
],
});

複数のストレージとの同期

atom が常に複数のストレージシステムと同期することが望ましい場合があります。たとえば、一部の UI 状態の atom は、共有可能な URL の現在の状態を永続化すると同時に、クラウドに保存されたユーザーごとのデフォルトと同期したい場合があります。これは、複数の atom エフェクトを構成するだけで実行できます(syncEffect() または他の atom エフェクトを使用して組み合わせることができます)。エフェクトは順番に実行されるため、最後のものが atom を初期化する優先順位が高くなります。

const currentTabState = atom<string>({
key: 'CurrentTab',
default: 'FirstTab', // Fallback default for first-use
effects: [
// Initialize default with per-user default from the cloud
syncEffect({ storeKey: 'user_defaults', refine: string() }),

// Override with state stored in URL if reloading or sharing
syncEffect({ storeKey: 'url', refine: string() }),
],
});

抽象ストア

同じ atom が、ホスト環境に応じて異なるストレージと同期する場合もあります。例:

const currentUserState = atom<number>({
key: 'CurrentUser',
default: 0,
effects: [
syncEffect({ storeKey: 'ui_state', refine: number() }),
],
});

スタンドアロンアプリは、その atom を URL と同期する可能性があります。

function MyStandaloneApp() {
return (
<RecoilRoot>
<RecoilURLSyncTransit storeKey="ui_state" location={{part: 'hash'}}>
...
</RecoilURLSyncTransit>
</RecoilRoot>
);
}

同じ atom を使用するコンポーネントを使用する別のアプリは、ローカルストレージと同期したい場合があります。

function AnotherApp() {
return (
<RecoilRoot>
<RecoilSyncLocalStorage storeKey="ui_state">
...
</RecoilSyncLocalStorage>
</RecoilRoot>
)
}

高度な Atom マッピング

Atoms は、外部ストアのアイテムに1対1でマップされない場合があります。この例では、キーのアップグレードを実装するために read を使用する方法について説明します。syncEffect()read および write オプションを使用して、より複雑なマッピングを実装できます。

高度なマッピングでは、順序の問題が発生したり、atoms が同じアイテムを上書きしようとしたりする可能性があるため、注意が必要です。

多対一

複数の外部アイテムから状態を取得する atom の例

function manyToOneSyncEffect() {
syncEffect({
refine: object({ foo: nullable(number()), bar: nullable(number()) }),
read: ({read}) => ({foo: read('foo'), bar: read('bar')}),
write: ({write, reset}, newValue) => {
if (newValue instanceof DefaultValue) {
reset('foo');
reset('bar');
} else {
write('foo', newValue.foo);
write('bar', newValue.bar);
}
},
});
}

atom<{foo: number, bar: number}>({
key: 'MyObject',
default: {},
effects: [manyToOneSyncEffect()],
});

一対多

複合外部オブジェクトの prop から状態を取得するエフェクトの例

function oneToManySyncEffect(prop: string) {
const validate = assertion(dict(nullable(number())));
syncEffect({
refine: nullable(number()),
read: ({read}) => validate(read('compound'))[prop],
write: ({write, read}, newValue) => {
const compound = {...validate(read('compound'))};
if (newValue instanceof DefaultValue) {
delete compound[prop];
write('compound', compound);
} else {
write('compound', {...compound, [prop]: newValue});
}
},
});
}

atom<number>({
key: 'MyNumber',
default: 0,
effects: [oneToManySyncEffect('foo')],
});