Pourquoi vous ne devriez pas utiliser les fonctions de flèche en ligne dans les accessoires JSX
L'utilisation de fonctions fléchées ou de liaison dans JSX est une mauvaise pratique qui nuit aux performances, car la fonction est recréée à chaque rendu.
Chaque fois qu'une fonction est créée, la fonction précédente est garbage collection. Le rendu de nombreux éléments peut créer des saccades dans les animations.
L'utilisation d'une fonction de flèche en ligne provoquera le rendu de PureComponent
s et des composants utilisés shallowCompare
dans la shouldComponentUpdate
méthode. Étant donné que l'accessoire de fonction de flèche est recréé à chaque fois, la comparaison superficielle l'identifiera comme une modification apportée à un accessoire et le composant sera renvoyé.
Comme vous pouvez le voir dans les 2 exemples suivants - lorsque nous utilisons la fonction de flèche en ligne, le <Button>
composant est rendu à chaque fois (la console affiche le texte du 'bouton de rendu').
Exemple 1 - PureComponent sans gestionnaire en ligne
class Button extends React.PureComponent {
render() {
const { onClick } = this.props;
console.log('render button');
return (
<button onClick={ onClick }>Click</button>
);
}
}
class Parent extends React.Component {
state = {
counter: 0
}
onClick = () => this.setState((prevState) => ({
counter: prevState.counter + 1
}));
render() {
const { counter } = this.state;
return (
<div>
<Button onClick={ this.onClick } />
<div>{ counter }</div>
</div>
);
}
}
ReactDOM.render(
<Parent />,
document.getElementById('root')
);
<script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Exemple 2 - PureComponent avec un gestionnaire en ligne
class Button extends React.PureComponent {
render() {
const { onClick } = this.props;
console.log('render button');
return (
<button onClick={ onClick }>Click</button>
);
}
}
class Parent extends React.Component {
state = {
counter: 0
}
render() {
const { counter } = this.state;
return (
<div>
<Button onClick={ () => this.setState((prevState) => ({
counter: prevState.counter + 1
})) } />
<div>{ counter }</div>
</div>
);
}
}
ReactDOM.render(
<Parent />,
document.getElementById('root')
);
<script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Liaison de méthodes à des this
fonctions fléchées sans insertion
Lier la méthode manuellement dans le constructeur:
class Button extends React.Component {
constructor(props, context) {
super(props, context);
this.cb = this.cb.bind(this);
}
cb() {
}
render() {
return (
<button onClick={ this.cb }>Click</button>
);
}
}
Lier une méthode à l'aide des champs de classe de proposition avec une fonction de flèche. Comme il s'agit d'une proposition de l'étape 3, vous devrez ajouter le préréglage de l' étape 3 ou la transformation des propriétés de classe à votre configuration babel.
class Button extends React.Component {
cb = () => { // the class property is initialized with an arrow function that binds this to the class
}
render() {
return (
<button onClick={ this.cb }>Click</button>
);
}
}
Composants de fonction avec rappels internes
Lorsque nous créons une fonction interne (gestionnaire d'événements par exemple) à l'intérieur d'un composant de fonction, la fonction sera recréée à chaque fois que le composant est rendu. Si la fonction est passée en tant qu'accessoires (ou via le contexte) à un composant enfant ( Button
dans ce cas), cet enfant sera également rendu.
Exemple 1 - Composant de fonction avec un rappel interne:
const { memo, useState } = React;
const Button = memo(({ onClick }) => console.log('render button') || (
<button onClick={onClick}>Click</button>
));
const Parent = () => {
const [counter, setCounter] = useState(0);
const increment = () => setCounter(counter => counter + 1); // the function is recreated all the time
return (
<div>
<Button onClick={increment} />
<div>{counter}</div>
</div>
);
}
ReactDOM.render(
<Parent />,
document.getElementById('root')
);
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root"></div>
Pour résoudre ce problème, nous pouvons encapsuler le rappel avec le useCallback()
hook et définir les dépendances sur un tableau vide.
Remarque: la useState
fonction générée accepte une fonction de mise à jour, qui fournit l'état actuel. De cette façon, nous n'avons pas besoin de définir l'état actuel comme une dépendance de useCallback
.
Exemple 2 - Composant de fonction avec un rappel interne enveloppé avec useCallback:
const { memo, useState, useCallback } = React;
const Button = memo(({ onClick }) => console.log('render button') || (
<button onClick={onClick}>Click</button>
));
const Parent = () => {
const [counter, setCounter] = useState(0);
const increment = useCallback(() => setCounter(counter => counter + 1), []);
return (
<div>
<Button onClick={increment} />
<div>{counter}</div>
</div>
);
}
ReactDOM.render(
<Parent />,
document.getElementById('root')
);
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root"></div>
this
, il n'y a donc rien à lier. Habituellement, les méthodes sont fournies par un composant intelligent wrapper.onClick={() => { onTodoClick(todo.id) }
cb() { onTodoClick(this.props.todo.id); }
.useCallback
avec une valeur dynamique. stackoverflow.com/questions/55006061/…C'est parce qu'une fonction de flèche créera apparemment une nouvelle instance de la fonction sur chaque rendu si elle est utilisée dans une propriété JSX. Cela pourrait créer une énorme pression sur le ramasse-miettes et empêchera également le navigateur d'optimiser les "chemins chauds" puisque les fonctions seront jetées au lieu d'être réutilisées.
Vous pouvez voir toute l'explication et quelques informations supplémentaires sur https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md
la source
bind
fonctions ou des flèches consiste ici à vous tirer une balle dans le pied. Ce n'est pas bien documenté cependant, en particulier dans le cas de l'utilisation demap
tableaux de ping dans les listes, etc.Pour éviter de créer de nouvelles fonctions avec les mêmes arguments, vous pouvez mémoriser le résultat de la liaison de fonction, voici un simple utilitaire nommé
memobind
pour le faire: https://github.com/supnate/memobindla source
Utiliser des fonctions en ligne comme celle-ci est parfaitement bien. La règle du peluchage est dépassée.
Cette règle date d'une époque où les fonctions fléchées n'étaient pas aussi courantes et où les gens utilisaient .bind (this), qui était lent. Le problème de performances a été résolu dans Chrome 49.
Faites attention à ne pas transmettre de fonctions en ligne comme accessoires à un composant enfant.
Ryan Florence, l'auteur de React Router, a écrit un excellent article à ce sujet:
https://cdb.reacttraining.com/react-inline-functions-and-performance-bdff784f5578
la source
Vous pouvez utiliser les fonctions fléchées à l'aide de la bibliothèque react-cached-handler , pas besoin de s'inquiéter des performances de re-rendu:
Autres caractéristiques:
la source
Pourquoi les accessoires JSX ne devraient-ils pas utiliser les fonctions fléchées ou se lier?
Surtout, parce que les fonctions en ligne peuvent interrompre la mémorisation des composants optimisés:
Il s'agit moins d'un coût de création de fonction supplémentaire:
Quand la
react/jsx-no-bind
règle est-elle utile?Vous voulez vous assurer que les composants mémorisés fonctionnent comme prévu:
React.memo
(pour les composants de fonction)PureComponent
ou personnaliséshouldComponentUpdate
(pour les composants de classe)En obéissant à cette règle, des références d'objet de fonction stables sont transmises. Ainsi, les composants ci-dessus peuvent optimiser les performances en empêchant les ré-rendus, lorsque les accessoires précédents n'ont pas changé.
Comment résoudre l'erreur ESLint?
Classes: définissez le gestionnaire comme méthode ou propriété de classe pour la
this
liaison.Crochets: utilisez
useCallback
.Terrain d'entente
Dans de nombreux cas, les fonctions en ligne sont très pratiques à utiliser et absolument parfaites en termes d'exigences de performances. Malheureusement, cette règle ne peut pas être limitée aux seuls types de composants mémorisés. Si vous souhaitez toujours l'utiliser dans tous les domaines, vous pouvez par exemple le désactiver pour les nœuds DOM simples:
la source