-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Recoil #185
Comments
React用のステート管理ライブラリ。 2021/08/18現在、まだ試験的な段階でありプロダクトでの使用は避けた方が良い。 Reduxにあった「アクション」と言う概念がないため、予測可能性がReduxと比べて弱い。
|
アトムステートの単位。 const fontSizeState = atom({
key: 'fontSizeState',
default: 14,
}); アトムには一意のキーを設定する必要がある。 アトムの値を読み書きするには function FontButton() {
const [fontSize, setFontSize] = useRecoilState(fontSizeState);
return (
<button onClick={() => setFontSize((size) => size + 1)} style={{fontSize}}>
Click to Enlarge
</button>
);
}
アトムファミリーアトムを返す関数を作成する。
const counterStateFamily = atomFamily({
key: 'counterState',
default: 0,
});
const Component1 = () => {
const counter = useRecoilValue(counterStateFamily(1));
return <div>{counter}</div>;
};
const Component2 = () => {
const counter = useRecoilValue(counterStateFamily(2));
return <div>{counter}</div>;
}; Component1とComponent2は別々のアトムを持つ。 パラメータを元にデフォルト値を設定することも可能。 const myAtomFamily = atomFamily({
key: ‘MyAtom’,
default: param => defaultBasedOnParam(param),
}); ステートにもアクセスしたい場合は selectorFamily を併用する。パラメータにもアクセス可能。 const myAtomFamily = atomFamily({
key: ‘MyAtom’,
default: selectorFamily({
key: 'MyAtom/Default',
get: param => ({get}) => {
const otherAtomValue = get(otherState);
return computeDefaultUsingParam(otherAtomValue, param);
},
}),
}); パラメータごとにアトムを作成するからアトムファミリー(アトムの集まり)なんだと思われる。 セレクタアトムや他のセレクタを基にデータを作成する。 Vueでいう computed みたいな感じ? アトムとセレクタは同じAPIを持つため、コンポーネントからは両方とも同じように扱える。(代替も可能) const fontSizeLabelState = selector({
key: 'fontSizeLabelState',
get: ({get}) => {
const fontSize = get(fontSizeState);
const unit = 'px';
return `${fontSize}${unit}`;
},
});
上記の例は一言で言うと、 コンポーネントからセレクタの値を参照するには function FontButton() {
const [fontSize, setFontSize] = useRecoilState(fontSizeState);
const fontSizeLabel = useRecoilValue(fontSizeLabelState);
return (
<>
<div>Current font size: {fontSizeLabel}</div>
<button onClick={() => setFontSize(fontSize + 1)} style={{fontSize}}>
Click to Enlarge
</button>
</>
);
} セレクタファミリーパラメータを渡してセレクタを返す関数を作成する。 const userNameQuery = selectorFamily({
key: 'UserName',
get: userID => async () => {
const response = await myDBQuery({userID});
if (response.error) {
throw response.error;
}
return response.name;
},
}); const userName = useRecoilValue(userNameQuery(userID)); アトムやセレクタの変化だけでなく、パラメータの変化によってもセレクタが再評価される。 パラメータごとにセレクタを作成するからセレクタファミリー(セレクタの集まり)なんだと思われる。 |
Getting Started
ES5にトランスパイルされていないため必要な場合は別途Babelでのトランスパイルが必要。 Recoilを使用したサンプル CDN
|
RecoilRootRecoilはRecoilRootコンポーネント内でのみ使用できる。 import React from 'react';
import { RecoilRoot } from 'recoil';
function App() {
return (
<RecoilRoot>
<div>Hello</div>
</RecoilRoot>
);
} 入れ子にすることも可能。 |
非同期データクエリセレクタの const currentUserNameQuery = selector({
key: 'CurrentUserName',
get: async ({get}) => {
const response = await myDBQuery({
userID: get(currentUserIDState),
});
return response.name;
},
});
function CurrentUserInfo() {
const userName = useRecoilValue(currentUserNameQuery);
return <div>{userName}</div>;
} 入力ごとに出力がキャッシュされるため、入力が同じ場合は 使う側(つまり 非同期の function MyApp() {
return (
<RecoilRoot>
<React.Suspense fallback={<div>Loading...</div>}>
<CurrentUserInfo />
</React.Suspense>
</RecoilRoot>
);
}
function UserInfo({userID}) {
const userNameLoadable = useRecoilValueLoadable(userNameQuery(userID));
switch (userNameLoadable.state) {
case 'hasValue':
return <div>{userNameLoadable.contents}</div>;
case 'loading':
return <div>Loading...</div>;
case 'hasError':
throw userNameLoadable.contents;
}
} エラーハンドリングPromiseのrejectはエラーハンドリング用のコンポーネントを作ってキャッチする。 同時並行リクエスト複数の非同期処理を同時に評価したい場合は const friendsInfoQuery = selector({
key: 'FriendsInfoQuery',
get: ({get}) => {
const {friendList} = get(currentUserInfoQuery);
return friendList.map(friendID => get(userInfoQuery(friendID)));
},
}); 上記の例では const friendsInfoQuery = selector({
key: 'FriendsInfoQuery',
get: ({get}) => {
const {friendList} = get(currentUserInfoQuery);
const friends = get(waitForAll(
friendList.map(friendID => userInfoQuery(friendID))
));
return friends;
},
});
const friendsInfoQuery = selector({
key: 'FriendsInfoQuery',
get: ({get}) => {
const {friendList} = get(currentUserInfoQuery);
const friendLoadables = get(waitForNone(
friendList.map(friendID => userInfoQuery(friendID))
));
return friendLoadables
.filter(({state}) => state === 'hasValue')
.map(({contents}) => contents);
},
}); Query Refresh可変データを非同期で取得する方法。 リクエストIDセレクタの入力にリクエストIDを追加することで、セレクタは冪等でなければならないルールを守りつつ可変データの取得ができる。 const requestIdState = atom({
key: 'requestIdState',
default: 0,
});
const newsFeedQuery = selector({
key: 'newsFeedQuery',
get: async ({get}) => {
const requestId = get(requestIdState);
const newsFeed = await myDbAccess();
return newsFeed;
}
})
function App() {
const setRequestId = useSetRecoilState(requestIdState);
const newsFeed = useRecoilValue(newsFeedQuery);
const onClick = () => setRequestId(old => old + 1);
return (
<div>
{newsFeed}
<button onClick={onClick}>更新</button>
</div>
);
} アトムを使用する単純にアトムを更新する方法。 |
useRecoilCallbackuseRecoilCallback(callback, deps) ReactのuseCallbackのRecoil版。
|
スナップショットアトムやセレクタで形成されたデータグラフのある時点でのスナップショット。不変。 スナップショットの取得useRecoilCallback(callback, deps)スナップショットにアクセスできる関数を作成する。 const callback = useRecoilCallback(function({snapshot}) { // コールバック関数のラッパー。snapshotを提供する。
return function() { // コールバック関数
console.log(snapshot); // スナップショットにアクセスできる
};
}); 引数として与えた 戻り値の関数の呼び出しをトリガーとしてスナップショットの取得を行う。 例えばボタンを押すたびにネットアクセスを行いAPIからデータを取得するような場合、useRecoilValueやuseRecoilSnapshotでは実装できない。(ただのコールバック内からはuseRecoilValueやuseRecoilSnapshotは仕様上使えない) 何かのイベントをトリガーとするときなど、任意のタイミングでステートにアクセスしたい場合に 以下はNG。useRecoilValueはアロー関数式の中では使用できない。(フックはコンポーネント直下の処理の中でしか使用できない) const [counter, setCounter] = useState(0);
const onClick = () => {
const amount = useRecoilValue(amountState);
setCounter(old => old + amount);
} 以下はOK。 const [counter, setCounter] = useState(0);
const onClick = useRecoilCallback(({snapshot}) => () => {
const amount = await snapshot.getPromise(amountState);
setCounter(old => old + amount);
}); 引数の const onClick = useRecoilCallback(({snapshot}) => {
const amount = await snapshot.getPromise(amountState);
setCounter(old => old + amount);
}); 以下が正しい。(スナップショットを受け取った関数は関数を返し、その関数の中に処理を書いている) const onClick = useRecoilCallback(({snapshot}) => () => {
const amount = await snapshot.getPromise(amountState);
setCounter(old => old + amount);
}); useRecoilSnapshot()同期的にスナップショットを取得する。 const snapshot = useRecoilSnapshot();
for(const node of snapshot.getNodes_UNSTABLE()) {
console.log(snapshot.getLoadable(node));
} useRecoilTransactionObserver_UNSTABLE()ステートの変更をサブスクライブし、変更がある度にスナップショットを取得する。 スナップショットの生成snapshot_UNSTABLE()スナップショットを生成する。 スナップショットから値を読み込むgetLoadableステートをLoadableとして取得する getPromise非同期セレクタの値を取得する スナップショットから新しいスナップショットを生成するmapスナップショットをステートに反映するuseGotoRecoilSnapshot() |
アトムエフェクトアトムに副作用を追加する試験段階のAPI。 アトムは アトムファミリーにアトムエフェクトを設定した場合はそれぞれのアトムごとにエフェクトが適用される。 const myState = atom({
key: 'MyKey',
default: null,
effects_UNSTABLE: [
() => {
...effect 1...
return () => ...cleanup effect 1...;
},
() => { ...effect 2... },
],
});
アトムエフェクト関数の型は以下の通り。 type AtomEffect<T> = ({
node: RecoilState<T>,
trigger: 'get' | 'set',
setSelf: (
| T
| DefaultValue
| Promise<T | DefaultValue>
| ((T | DefaultValue) => T | DefaultValue),
) => void,
resetSelf: () => void,
onSet: (
(newValue: T, oldValue: T | DefaultValue, isReset: boolean) => void,
) => void,
getPromise: <S>(RecoilValue<S>) => Promise<S>,
getLoadable: <S>(RecoilValue<S>) => Loadable<S>,
getInfo_UNSTABLE: <S>(RecoilValue<S>) => RecoilValueInfo<S>,
}) => void | () => void; アトムエフェクト関数が関数を返す場合、それはクリーンアップ用の関数として使用される。 |
useRecoilTransactionトランザクションは複数のアトムをアトミックに更新するための手段。 interface TransactionInterface {
get: <T>(RecoilValue<T>) => T;
set: <T>(RecoilState<T>, (T => T) | T) => void;
reset: <T>(RecoilState<T>) => void;
}
function useRecoilTransaction_UNSTABLE<Args>(
callback: TransactionInterface => (...Args) => void,
deps?: $ReadOnlyArray<mixed>,
): (...Args) => void 引数の セレクタはサポートしていない。(今後サポートされる可能性あり) 例 function App() {
const kouza = useRecoilValue(kouzaState);
const saifu = useRecoilValue(saifuState);
const [amount, setAmount] = useState(0);
const onChangeAmount = e => setAmount(e.target.value - 0);
const onClickAzukeire = useRecoilTransaction_UNSTABLE(({set}) => (amount) => {
set(kouzaState, old => old + amount);
set(saifuState, old => old - amount);
}, []);
const onClickHikiotoshi = useRecoilTransaction_UNSTABLE(({set}) => (amount) => {
set(kouzaState, old => old - amount);
set(saifuState, old => old + amount);
}, [])
return (
<div>
口座:{kouza}円<br />
財布:{saifu}円<br />
<input type="text" value={amount} onChange={onChangeAmount} /><br />
<button onClick={() => onClickAzukeire(amount)}>預け入れ</button>
<button onClick={() => onClickHikiotoshi(amount)}>引き落とし</button>
</div>
);
} Reduxのアクションとリデューサを再現することもできる。 |
constSelector常に同じ値を返すセレクタ function constSelector<T: Parameter>(constant: T): RecoilValueReadOnly<T> 定数でいいのにRecoilValue型を使用しないといけない、という時に便利。 |
errorSelector常にあたえられたエラーを投げるセレクタ function errorSelector(message: string): RecoilValueReadOnly |
LoadableLoadableオブジェクトはアトムやセレクタの「現在の値」を表す。
Loadableオブジェクトは以下のインターフェースを持つ。
Loadableオブジェクトは以下のヘルパーメソッドを持つが、安定した(stableな)APIではない。
|
noWait()function noWait<T>(state: RecoilValue<T>): RecoilValueReadOnly<Loadable<T>> 「アトムやセレクタ」を、「Loadableをラップするセレクタ」に変換するヘルパー関数。 const myQuery = selector({
key: 'MyQuery',
get: ({get}) => {
const loadable = get(noWait(dbQuerySelector));
return {
hasValue: {data: loadable.contents},
hasError: {error: loadable.contents},
loading: {data: 'placeholder while loading'},
}[loadable.state];
}
}) waitForAll()function waitForAll(dependencies: Array<RecoilValue<>>): RecoilValueReadOnly<UnwrappedArray>
function waitForAll(dependencies: {[string]: RecoilValue<>}): RecoilValueReadOnly<UnwrappedObject> 複数のアトムやセレクタを同時に評価するセレクタを作成するヘルパー関数。 waitForAllSettled()function waitForAllSettled(dependencies: Array<RecoilValue<>>): RecoilValueReadOnly<UnwrappedArrayOfLoadables>
function waitForAllSettled(dependencies: {[string]: RecoilValue<>}): RecoilValueReadOnly<UnwrappedObjectOfLoadables> 複数のアトムやセレクタを同時に評価し、それぞれのLoadableを返すセレクタを作成するヘルパー関数。 この関数はそれぞれのLoadableが全て解決またはエラーとなるまで待機する。 waitForNone()function waitForNone(dependencies: Array<RecoilValue<>>): RecoilValueReadOnly<UnwrappedArrayOfLoadables>
function waitForNone(dependencies: {[string]: RecoilValue<>}): RecoilValueReadOnly<UnwrappedObjectOfLoadables> 複数のアトムやセレクタを同時に評価し、それぞれのLoadableを返すセレクタを作成するヘルパー関数。 「wait for none」は「何も待たない」という意味らしい。 以下は解決済みのものから段階的に表示していく例。 function MyChart({layerQueries}: {layerQueries: Array<RecoilValue<Layer>>}) {
const layerLoadables = useRecoilValue(waitForNone(layerQueries));
return (
<Chart>
{layerLoadables.map((layerLoadable, i) => {
switch (layerLoadable.state) {
case 'hasValue':
return <Layer key={i} data={layerLoadable.contents} />;
case 'hasError':
return <LayerErrorBadge key={i} error={layerLoadable.contents} />;
case 'loading':
return <LayerWithSpinner key={i} />;
}
})}
</Chart>
);
} waitForAny()function waitForAny(dependencies: Array<RecoilValue<>>): RecoilValueReadOnly<UnwrappedArrayOfLoadables>
function waitForAny(dependencies: {[string]: RecoilValue<>}): RecoilValueReadOnly<UnwrappedObjectOfLoadables> 複数のアトムやセレクタを同時に評価し、それぞれのLoadableを返すセレクタを作成するヘルパー関数。 少なくとも一つのLoadableが解決済みまたはエラーになるまで待機する。 |
useRecoilRefresherhttps://recoiljs.org/docs/api-reference/core/useRecoilRefresher/ Query Refresh の一つとして Recoil 0.5 で追加されたフック。 セレクタが別のセレクタに依存している場合、再帰的にキャッシュが削除される。 (セレクタは冪等でなければいけなかったのではないのかという疑問が残る。) |
https://recoiljs.org/
The text was updated successfully, but these errors were encountered: