Dans React, j'ai souvent besoin d'utiliser quelque chose comme useCallback
pour mémoriser une fonction dans une liste d'éléments (créée via une boucle) afin d'éviter de restituer tous les composants si un seul élément change en raison d'un identifiant de référence incompatible... Malheureusement, il est étonnamment difficile d'expirer. Par exemple, considérons le code suivant :
const MyComp = memo({elements} => { { elements.map((elt, i) => { <>{elt.text}<Button onClick={(e) => dispatch(removeElement({id: i}))}> <> }) } })
où Button
se trouvent les composants externes fournis par Ant Design, etc. Cette référence de fonction sera alors différente à chaque rendu car elle est en ligne, forçant ainsi un nouveau rendu.
Pour éviter ce problème, je peux penser à une autre solution : créer un nouveau composant 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}> <> }) } )
Bien que cela fonctionne, cela est très peu pratique pour plusieurs raisons :
Button
tous les éléments dans des bibliothèques externes telles que <MyButton index1={index1} index2={index2} index3={index3 onClick={myFunction}>
,这意味着我需要完全通用地创建一个更复杂的版本 MyButton
来检查嵌套级别的数量。我无法使用 index={[index1,index2,index3]}
Cette combinaison est mauvaise : si je voulais imbriquer les éléments dans plusieurs listes, ce serait encore plus sale car il me faudrait ajouter un nouvel index à chaque niveau de la liste, comme index
Pour autant que je sache, il n'y a pas de convention de dénomination pour les Est-ce qu'il me manque une meilleure solution ? Étant donné que les listes sont partout, je ne peux pas croire qu'il n'y ait pas de solution appropriée à ce problème, et je suis surpris de voir le peu de documentation disponible à ce sujet.
Modifier
J'essaye ça :
// 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} /> });et utilisez-le comme ceci :
onClick
,onChange
,...),如果我有它就无法直接工作多个属性(例如 onClick
和 onChange
const myactionIndexed = useCallback((e, i) => dispatch(removeSolverConstraint({id: i})), []); return <WrapperOnClick index={i} Component={Button} onClick={myactionIndexed} danger><CloseCircleOutlined /></WrapperOnClick>Mais ce n'est toujours pas parfait, j'ai notamment besoin d'un wrapper pour différents niveaux d'imbrication, et chaque fois que je cible une nouvelle propriété, je dois créer une nouvelle version (), je n'ai jamais vu cela auparavant, donc je veux une meilleure solution .
Modifier
J'ai essayé diverses idées, notamment l'utilisation de fast-memoize, mais je ne comprends toujours pas tous les résultats : parfois, fast-memoize fonctionne, parfois cela échoue... et je ne sais pas si fast-memoize est une solution recommandée : Il semble étrange d’utiliser un outil tiers pour un cas d’utilisation aussi courant. Découvrez mes tests ici https://codesandbox.io/embed/magical-dawn-67mgxp?fontsize=14&hidenavigation=1&theme=dark🎜
Testez ici https://codesandbox.io /s/sharp-wind-rd48q4?file=/src/App.js
Attention : je ne suis pas un expert de React (d'où ma question !), alors merci de commenter ci-dessous et/ou d'ajouter un +1 si vous pensez que cette solution est la manière canonique de le faire dans React (ou -1 si ce n'est pas le cas) ^^ ). Je suis également curieux de savoir pourquoi certaines autres solutions échouent (par exemple, basées sur la mémorisation proxy (ce qui prend en fait 10 fois plus de temps que l'absence de mise en cache et ne met pas du tout en cache) ou la mémorisation rapide (qui ne se met pas toujours en cache, selon la façon dont Je l'utilise )), donc si vous savez, je suis intéressé de savoir)
Comme ce problème ne m'intéresse pas beaucoup, j'ai essayé de comparer un tas de solutions (14 !) avec diverses options (pas de mémoire, utilisation de bibliothèques externes (mémoire rapide vs mémoire proxy), utilisation de wrappers), utilisation de composants externes, etc...
La meilleure façon semble être de créer un nouveau composant contenant l'intégralité de l'élément de la liste, pas seulement le dernier bouton. Cela permet d'obtenir un code assez propre (même si je dois créer deux composants pour la liste et l'élément, au moins cela a du sens sémantiquement), évite les bibliothèques externes et semble être plus efficace que tout ce que j'ai essayé (au moins à mon avis (par exemple) :
Je n'aime toujours pas vraiment cette solution car j'ai besoin de transférer beaucoup de contenu du composant parent au composant enfant, mais cela semble être la meilleure solution que je puisse obtenir...
Vous pouvez voir une liste de mes tentatives ici, j'ai utilisé le code ci-dessous. Voici la vue du profileur (techniquement, la différence de temps entre toutes les versions n'est pas si grande (sauf la version 7 qui utilise proxy-memoize, je l'ai supprimée car elle était plus longue, peut-être 10x, et était en cours de création. Les graphiques sont plus difficiles à lire) , mais je m'attends à ce que cette différence soit plus grande sur les listes plus longues, où les éléments sont plus complexes à dessiner (ici je n'ai qu'un seul texte et un seul bouton). Notez que toutes les versions ne sont pas exactement les mêmes (certaines utilisent
,一些
, certaines listes normales, certaines listes conçues par Ant...), donc les comparaisons temporelles n'ont de sens qu'entre les versions qui effectuent la même opération. Quoi qu'il en soit, ma principale préoccupation est de voir ce qui est mis en cache et ce qui ne l'est pas, ce qui est clairement visible dans le profileur (les blocs gris clair sont mis en cache) :
Un autre fait intéressant est que vous souhaiterez peut-être faire un benchmark avant de mémoriser, car l'amélioration peut ne pas être significative, du moins pour les composants simples (taille 5 ici, juste un texte et un bouton).