restate / v1.0.0
只订阅需要的部分
选择每个组件依赖的 store 部分,用 shallow 比较对象,用 batch 合并变化,并接入外部事件。
传入 selector
调用 Hook 时传入一个函数,挑出组件需要的内容。每次变化后,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 都会立即通知订阅者。当一个操作要改动多个 store 时,用 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。它在第一个组件或订阅者开始监听时启动,在最后一个停止监听时执行清理,因此浏览器事件、socket 或传感器只在需要时才连接。
用 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);
};
});