在反應中,我確實經常需要使用諸如useCallback
之類的內容來記憶項目列表中的函數(透過循環創建),以避免由於引用標識符不匹配而導致單個元素發生更改而重新渲染所有組件……不幸的是,這是令人驚訝的是很難到期。例如,考慮以下程式碼:
const MyComp = memo({elements} => { { elements.map((elt, i) => { <>{elt.text}<Button onClick={(e) => dispatch(removeElement({id: i}))}> <> }) } })
其中 Button
是由 ant design 等提供的外部元件。然後,這個函數引用在每次渲染時都會不同,因為它是內聯的,因此強制重新渲染。
為了避免這個問題,我可以想到另一個解決方案:建立一個新元件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}> <> }) } )
雖然這確實有效,但由於多種原因,這是非常不實用的:
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>但這仍然不完美,特別是我需要一個用於不同嵌套層級的包裝器,每當我定位一個新屬性時我都需要創建一個新版本(
onClick
,onChange
,...),如果我有它就無法直接工作多個屬性(例如onClick
和onChange
),我以前從未見過這個,所以我想有更好的解決方案。
編輯 我嘗試了各種想法,包括使用fast-memoize,但我仍然不明白所有結果:有時,fast-memoize 有效,有時失敗......而且我不知道fast-memoize 是否是建議的解決方案:似乎對於如此常見的用例使用第三方工具很奇怪。在這裡查看我的測試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,只有一個文字和一個按鈕)。