restate / v1.0.0
必要な部分だけを購読する
コンポーネントが依存するストアの部分を選び、shallow でオブジェクトを比較し、batch で変更をまとめ、外部のイベントをつなぎます。
selectorを渡す
コンポーネントが必要とするものを選ぶ関数を渡してフックを呼びます。変更のたびに restate はselectorを実行し、結果を前回の結果と Object.is で比較します。等しければ、そのコンポーネントはレンダリングされません。
メッセージが変わりました。ItemCount はレンダリングされるでしょうか?
いいえ。selectorは同じ商品数を返し続けるので選択結果は等しく、React にレンダリングは依頼されません。
const useCart = restate({ items: ['Book'], coupon: '', note: '' });
function ItemCount() {
// Re-renders only when the number of items changes.
const count = useCart((cart) => cart.items.length);
return <p>{count} items</p>;
}試してみる
フィールドを変えて、レンダリングのカウンターを見てください。shallow をオフにすると、毎回新しいオブジェクトを返すselectorの動きがわかります。
サンプルを準備中…
shallow で複数のフィールドを選択する
オブジェクトを組み立てるselectorは毎回新しいオブジェクトを返すので、Object.is は常に変更ありと判断します。2 番目の引数に shallow を渡すと、オブジェクト自身のフィールドを 1 階層だけ比較します。
shallow は配列、Map、Set の要素も 1 階層だけ比較します。
import { restate, shallow } from '@kkwaenggwari/restate';
function CartSummary() {
const { count, coupon } = useCart(
(cart) => ({ count: cart.items.length, coupon: cart.coupon }),
shallow,
);
return (
<p>
{count} items · {coupon || 'no coupon'}
</p>
);
}独自の比較関数を書く
2 番目の引数には任意の関数 (left, right) => boolean を渡せます。リストを id で比較するなど、shallow では足りないときに使います。
const ids = useCart(
(cart) => cart.items.map((item) => item.id),
(left, right) => left.length === right.length && left.every((id, index) => id === right[index]),
);batch で変更をまとめる
set はそのたびに購読者へ即座に通知します。1 つの操作で複数のストアを変えるときは batch で囲みます。メモリはすぐに更新され、購読者はコールバックの後に 1 回だけ実行されます。
import { batch } from '@kkwaenggwari/restate';
function checkout() {
batch(() => {
useCart.set((cart) => ({ ...cart, items: [] }));
useOrder.set((order) => ({ ...order, status: 'paid' }));
});
// Subscribers run once, after both stores changed.
}React の外で購読する
subscribeSelector は、選択した値が変わったときだけ呼び出されます。省略可能な比較関数も同じように使えます。
const stop = useCart.subscribeSelector(
(cart) => cart.items.length,
(count, previous) => analytics.track('cart-size', { count, previous }),
);外部のイベントをつなぐ
restate の 2 番目の引数には updater を渡せます。最初のコンポーネントや購読者が購読を始めたときに開始し、最後の 1 つが購読をやめるとクリーンアップが実行されるので、ブラウザーのイベント、ソケット、センサーは必要な間だけ接続されます。
手動で制御するには start() と stop() を使います。
import { restate } from '@kkwaenggwari/restate';
// Listens while at least one component or subscriber uses the store.
export const useOnline = restate(true, ({ setState }) => {
const update = () => setState(navigator.onLine);
window.addEventListener('online', update);
window.addEventListener('offline', update);
update();
return () => {
window.removeEventListener('online', update);
window.removeEventListener('offline', update);
};
});