In React muss ich oft so etwas wie useCallback
verwenden, um mir eine Funktion in einer Liste von Elementen (über eine Schleife erstellt) zu merken, um zu vermeiden, dass alle Komponenten erneut gerendert werden, wenn sich ein einzelnes Element aufgrund einer nicht übereinstimmenden Referenzkennung ändert ... Leider Das lässt sich erstaunlich schwer ablaufen. Betrachten Sie beispielsweise den folgenden Code:
const MyComp = memo({elements} => { { elements.map((elt, i) => { <>{elt.text}<Button onClick={(e) => dispatch(removeElement({id: i}))}> <> }) } })
wo Button
externe Komponenten sind, die von Ant Design usw. bereitgestellt werden. Diese Funktionsreferenz wird dann bei jedem Rendern unterschiedlich sein, da sie inline ist, was ein erneutes Rendern erzwingt.
Um dieses Problem zu vermeiden, fällt mir eine andere Lösung ein: Erstellen Sie eine neue Komponente MyButton
,它接受两个属性 index={i}
和 onClick
而不是单个 onClick
,并将参数 index
附加到任何调用onClick
:
const MyButton = ({myOnClick, id, ...props}) => { const myOnClickUnwrap = useCallback(e => myOnClick(e, id), [myOnClick]); return <Button onClick={myOnClickUnwrap} ...props/> }; const MyComp = memo({elements} => { const myOnClick = useCallback((e, id) => dispatch(removeElement({id: id})), []); return { elements.map((elt, i) => { <>{elt.text}<Button id={i} onClick={myOnClick}> <> }) } )
Das funktioniert zwar, ist aber aus mehreren Gründen sehr unpraktisch:
Button
umschließen <MyButton index1={index1} index2={index2} index3={index3 onClick={myFunction}>
,这意味着我需要完全通用地创建一个更复杂的版本 MyButton
来检查嵌套级别的数量。我无法使用 index={[index1,index2,index3]}
Diese Kombination ist schlecht: Wenn ich die Elemente in mehreren Listen verschachteln wollte, wäre es noch schmutziger, weil ich jeder Ebene der Liste einen neuen Index hinzufügen müsste, z. B. index
Soweit ich weiß, gibt es keine Namenskonvention für Verpasse ich eine bessere Lösung? Wenn man bedenkt, dass es überall Listen gibt, kann ich nicht glauben, dass es dafür keine richtige Lösung gibt, und ich bin überrascht, wie wenig Dokumentation es dazu gibt.
Bearbeiten
Ich versuche Folgendes:
// Define once: export const WrapperOnChange = memo(({onChange, index, Component, ...props}) => { const onChangeWrapped = useCallback(e => onChange(e, index), [onChange, index]); return <Component {...props} onChange={onChangeWrapped} /> }); export const WrapperOnClick = memo(({onClick, index, Component, ...props}) => { const onClickWrapped = useCallback(e => onClick(e, index), [onClick, index]); return <Component {...props} onClick={onClickWrapped} /> });und verwenden Sie es so:
onClick
,onChange
,...),如果我有它就无法直接工作多个属性(例如 onClick
和 onChange
const myactionIndexed = useCallback((e, i) => dispatch(removeSolverConstraint({id: i})), []); return <WrapperOnClick index={i} Component={Button} onClick={myactionIndexed} danger><CloseCircleOutlined /></WrapperOnClick>Aber es ist immer noch nicht perfekt, insbesondere brauche ich einen Wrapper für verschiedene Verschachtelungsebenen, und jedes Mal, wenn ich auf eine neue Eigenschaft abziele, muss ich eine neue Version erstellen (), das habe ich noch nie zuvor gesehen, also möchte ich eine bessere Lösung .
Bearbeiten
Ich habe verschiedene Ideen ausprobiert, darunter die Verwendung von Fast-Memoize, aber ich verstehe immer noch nicht alle Ergebnisse: Manchmal funktioniert Fast-Memoize, manchmal schlägt es fehl ... und ich weiß nicht, ob Fast-Memoize eine empfohlene Lösung ist: Es erscheint seltsam, für einen so häufigen Anwendungsfall ein Tool eines Drittanbieters zu verwenden. Schauen Sie sich meine Tests hier an: https://codesandbox.io/embed/magical-dawn-67mgxp?fontsize=14&hidenavigation=1&theme=dark🎜
这里的测试 https://codesandbox.io /s/sharp-wind-rd48q4?file=/src/App.js
警告:我不是 React 专家(因此我的问题!),所以请在下面发表评论和/或添加 +1,如果您认为此解决方案是在 React 中进行的规范方法(或 -1不是^^)。我也很好奇为什么其他一些解决方案失败了(例如基于 proxy-memoize(实际上比没有缓存长 10 倍,并且根本不缓存)或 fast-memoize(并不总是缓存,具体取决于如何我使用它)),所以如果你知道我有兴趣知道)
由于我对这个问题没什么兴趣,所以我尝试根据各种选择(无记忆、使用外部库(快速记忆与代理记忆)、使用包装器)对一堆解决方案(14!)进行基准测试,使用外部组件等...
最好的方法似乎是创建一个新组件包含列表的整个元素,而不仅仅是最后一个按钮。这允许相当干净的代码(即使我需要为列表和项目创建两个组件,至少它在语义上是有意义的),避免外部库,并且似乎比我尝试过的所有其他方法更有效(至少以我的例子为例):
我仍然不太喜欢这个解决方案,因为我需要将许多内容从父组件转发到子组件,但这似乎是我能得到的最佳解决方案......
您可以查看我的尝试列表 这里,我使用了下面的代码。这是探查器的视图(从技术上讲,所有版本之间的时间差异并不大(除了使用 proxy-memoize 的版本 7,我删除了它,因为它更长,也许是 10 倍,并且正在制作图表)更难阅读),但我预计这种差异在较长的列表上会更大,其中项目绘制起来更加复杂(这里我只有一个文本和一个按钮)。请注意,所有版本并不完全相同(有些版本使用
,一些
,一些普通列表,一些 Ant 设计列表...),所以时间比较只有在执行相同操作的版本之间才有意义。无论如何,我主要关心查看缓存的内容和未缓存的内容,这在分析器中清晰可见(缓存了浅灰色块):
另一个有趣的事实是,您可能希望在记忆之前进行基准测试,因为改进可能并不显着,至少对于简单的组件而言(此处大小为 5,只有一个文本和一个按钮)。