restate / v1.0.0
Подписывайтесь только на нужное
Выбирайте, от какой части хранилища зависит каждый компонент, сравнивайте объекты через shallow, группируйте изменения через batch и подключайте внешние события.
На этой странице
Передайте selectorПопробуйтеВыбор нескольких полей через shallowСвоя функция сравненияГруппировка изменений через batchПодписка вне ReactПодключение внешних событийПередайте 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 всегда видит изменение. Передайте shallow вторым аргументом: он сравнивает собственные поля объекта на один уровень вглубь.
shallow сравнивает на один уровень и элементы массивов, Map и Set.
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>
);
}Своя функция сравнения
Вторым аргументом подойдёт любая функция (left, right) => boolean. Используйте её, когда shallow недостаточно, например чтобы сравнивать список по id.
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 сразу уведомляет подписчиков. Когда одно действие меняет несколько хранилищ, оберните его в batch: память обновляется сразу, а подписчики срабатывают один раз после колбэка.
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 может быть updater. Он запускается, когда начинает слушать первый компонент или подписчик, а его очистка выполняется, когда перестаёт последний, — так события браузера, сокеты и датчики подключены только пока нужны.
Для ручного управления используйте 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);
};
});