问题
在react中,我真的经常需要使用useCallback
之类的东西在项目列表中记住函数(通过循环创建),以避免在单个元素更改时重新呈现所有组件,因为引用标识符不匹配...不幸的是,这是令人惊讶的难以到期。例如,考虑以下代码:
const MyComp = memo({elements} => {
{
elements.map((elt, i) => {
<>{elt.text}<Button onClick={(e) => dispatch(removeElement({id: i}))}> <>
})
}
})
其中Button
是由例如ANT设计提供的外部组件。然后,这个函数引用在每次渲染时都是不同的,因为它是内联的,因此强制重新渲染。
坏的解决方案
为了避免这个问题,我可以想到另一个解决方案:创建一个新的组件MyButton
,它接受两个prop 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}> <>
})
}
)
为什么我想要更好的方法
虽然这确实有效,但由于许多原因,这是非常不实际的:
- 代码混乱
- 我需要 Package 来自外部库(如
Button
)的所有元素,并重写不打算处理这种嵌套的组件。这就破坏了模块化,使代码更加复杂 - 这是一个很差的组成:如果我想在多个列表中嵌套元素,这将更加肮脏,因为我需要像
<MyButton index1={index1} index2={index2} index3={index3} onClick={myFunction}>
那样为列表的每一层添加一个新索引,这意味着我需要创建一个更复杂的MyButton
版本来检查嵌套层的数量。我不能使用index={[index1, index2, index3]}
,因为这是一个数组,因此没有稳定的引用。 - 据我所知,
index
es的命名没有约定,这意味着在项目之间共享代码或开发库更困难
有没有一个更好的解决方案我错过了吗?考虑到列表是如此无所不在,我不相信没有合适的解决方案,而且我很惊讶地看到很少有关于这方面的文档。
编辑我尝试做:
// 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} />
});
并像这样使用它:
const myactionIndexed = useCallback((e, i) => dispatch(removeSolverConstraint({id: i})), []);
return <WrapperOnClick index={i} Component={Button} onClick={myactionIndexed} danger><CloseCircleOutlined /></WrapperOnClick>
但这仍然不是完美的,特别是我需要一个 Package 器用于不同的嵌套级别,每当我针对一个新属性(onClick
,onChange
,...)时,我需要创建一个新版本,如果我有多个属性(例如,onChange
,...),它将无法直接工作。onClick
和onChange
),我以前从未见过这种情况,所以我猜有更好的解决方案。
edit我尝试了各种方法,包括使用fast-memoize,但我仍然不理解所有的结果:有时候,快速备忘录的作品,而有时它失败了…我不知道快速备忘录是否是推荐的解决方案:对于这样一个常见的用例使用第三方工具似乎很奇怪。在这里查看我的测试https://codesandbox.io/embed/magical-dawn-67mgxp?fontsize=14&hidenavigation=1&theme=dark
3条答案
按热度按时间rsl1atfo1#
警告:我不是ReactMaven(因此我的问题!),所以请在下面评论和/或添加+1,如果你认为这个解决方案是在React中进行的规范方式(或-1不是^^)。我也很想知道为什么其他一些解决方案失败了(例如。基于proxy-memoize(实际上是没有缓存的10倍长,并且根本不缓存)或fast-memoize(不总是缓存,取决于我如何使用它),所以如果你知道我有兴趣知道)
由于我对这个问题不感兴趣,所以我试着对一堆解决方案(14!),这取决于各种选择(无备忘录,使用外部库(快速备忘录vs代理备忘录),使用 Package 器,使用外部组件等...
最好的方法似乎是创建一个新的组件**,包含列表的整个元素**,而不仅仅是最后一个按钮。这使得代码相当干净(即使我需要为列表和项创建两个组件,至少在语义上是有意义的),避免了外部库,并且似乎比我尝试的所有其他方法更有效(至少在我的示例中):
我仍然不太喜欢这个解决方案,因为我需要将很多东西从父组件转发到子组件,但它似乎是我能得到的最好的解决方案。
你可以看到我的尝试列表here,我使用了下面的代码。这是侧写师的视角(从技术上讲,所有版本之间在时间方面没有很大的差异(除了使用代理记忆的版本7,我删除了它,因为它比它长了很多,也许是10倍,并且使图形更难阅读),但我希望这种差异在更长的列表中更大,其中项目更复杂(这里我只有一个文本和一个按钮)。请注意,并非所有版本都完全相同(有些版本使用
<button>
,有些版本使用</Button>
,有些版本使用普通列表,有些版本使用Ant设计列表......),因此时间比较只在做相同事情的版本之间有意义。无论如何,我最关心的是看到什么被缓存,什么没有,这在分析器中清晰可见(浅灰色块被缓存):另一个有趣的事实是,您可能希望在memoizing之前进行基准测试,因为改进可能并不显著,至少对于简单的组件(这里大小为5,只有一个文本和一个按钮)。
wnavrhmk2#
1.首先,不建议使用index作为参数或props或key,因为当你删除第一个时,所有的子组件都将重新渲染。
1.而根据你的场景如果想避免重新渲染,我有一些想法,大家可以参考一下,像这样:
这里的测试https://codesandbox.io/s/sharp-wind-rd48q4?file=/src/App.js
bqucvtff3#
为了解决这个问题,您可以使用react-fast-compare或lodash.isEqual等库来对useCallback中的依赖进行彻底的比较。这将消除对每个物品的唯一标识符的手动管理的需要。
//我的朋友
https://www.npmjs.com/package/react-fast-compare