我正在尝试使用信号(
@preact/signals-react
)来减少大型数据对象的重新渲染。就我而言,我从网络请求中获取对象,并且往往会随着实时更新而频繁更改。
对于直接属性,效果很好,减少了重新渲染的次数:
export function Root(){
const mySignal = useSignal({ sub: { val: 1 }, arr: [{id: 1, name: "bob"}] });
return <div>
<Counter signal={mySignal} />
<Array signal={mySignal} />
</div>
}
function Counter({ signal }) {
const counter = useComputed(() => signal.value.counter);
return (
<div>
<p>{counter}</p>
<button
onClick={() => {
signal.value = { ...signal.value, counter: signal.value.counter + 1 };
}}
>
Increment
</button>
</div>
);
}
在这种情况下,只有
Counter
在递增 👌 时重新渲染。useSignals()
时也是如此。
但是,对于数组,情况有所不同:
function ArrayConsumer({ mySignal }: { mySignal: MySignal }) {
const arr = useComputed(() => mySignal.value.someArr);
return (
<>
{arr.value.map((item) => (
// renders and edits {item.name}
<ArrayItem key={item.id} item={item} />
))}
</>
);
}
因为我直接使用 arr.value,所以每当数组更改时,整个列表都会重新渲染。 我期望某种
signalMap()
方法,但 docs 仅建议在那里使用 signal.value 。
一种解决方案是将各个列表项包装在信号中,但这似乎是一种不好的做法?我真的认为信号可以提供与 Mobx 相似的性能和 devexp,但如果没有列表支持,它的用处就小得多。
该库旨在成为一组核心原语,因此不涉及可以从用户空间添加的实用程序。这些东西很容易自己构建。这是 Jason/developit 不久前编写的一个有用的实用程序:
const Item = ({ v, k, f }) => f(v, k);
/**
* Like signal.value.map(fn), but doesn't re-render.
*/
export function For({ each, children: f, fallback }) {
let c = useMemo(() => new Map(), []);
return (
each.value?.map(
(v, k, x) => c.get(v) || (c.set(v, (x = <Item {...{ key: v, v, k, f }} />)), x)
) ?? fallback
);
}
现在,对于您的示例,您可以像这样使用它:
function ArrayConsumer({ mySignal }: { mySignal: MySignal }) {
const arr = useComputed(() => mySignal.value.someArr);
return (
<>
<For
each={arr}
children={(item) => (
<ArrayItem key={item.id} item={item} />
)}
/>
</>
);
}