62 votes

Utilisation correcte des fonctions flèches dans React

J'utilise ReactJS avec Babel et Webpack et j'utilise ES6 ainsi que l'option champs de classe proposés pour les fonctions de flèche. Je comprends que les fonctions flèches rendent les choses plus efficaces en ne pas recréer les fonctions à chaque rendu de la même manière que la liaison dans le constructeur fonctionne. Cependant, je ne suis pas sûr à 100% de les utiliser correctement. Ce qui suit est une section simplifiée de mon code dans trois fichiers différents.

Mon code :

Main.js

prevItem = () => {
    console.log("Div is clicked")
}

render(){
    return (
         <SecondClass prevItem={this.prevItem} />
    )
}

SecondClass.js

<ThirdClass type="prev" onClick={()=>this.props.prevItem()} />

ThirdClass.js

<div onClick={()=>{this.props.onClick()}}>Previous</div>

Question :

Mon code ci-dessus utilise-t-il correctement les fonctions de flèche ? J'ai remarqué que pour SecondClass.js j'aurais pu aussi utiliser :

<ThirdClass type="prev" onClick={this.props.prevItem} />

Y a-t-il une différence entre une méthode ou l'autre puisque j'ai utilisé une fonction flèche ES6 dans ma définition de fonction originale ? Ou devrais-je utiliser la syntaxe de la flèche jusqu'à ma dernière division ?

0 votes

0 votes

onClick={this.props.prevItem} ici puisqu'il n'y a pas de référence à this sur prevItem vous pouvez l'utiliser. Mais si vous ajoutez du code au niveau de l'étendue, il se cassera. Puisque vous assignez une fonction d'un objet et l'appelez, elle perd son contexte.

3 votes

Pour information, les champs de classe ne font pas partie de l'ES6.

67voto

Sagiv b.g Points 15448

Je comprends que les fonctions flèches rendent les choses plus efficaces en évitant de en ne recréant pas les fonctions à chaque fois qu'elles sont utilisées.

C'est pas vrai .

Les fonctions de la flèche gèrent les this le contexte d'une manière lexicale, là où une fonction "normale" le fait dynamiquement . J'ai écrit sur ce mot clé en profondeur si vous avez besoin de plus d'informations à ce sujet.

Dans vos deux exemples de la fonction flèche en ligne, vous créez une nouvelle instance de la fonction sur chaque render .
Ceci créera et passera une nouvelle instance à chaque rendu.

onClick={() => {}}

Dans le troisième exemple, vous n'avez qu'une seule instance.
Ceci ne fait que passer une référence à une instance déjà existante

onClick={this.myHandler}

Quant aux avantages des fonctions de flèches en tant que champs de classe (il y a une petit bémol (je l'afficherai en bas de la réponse), si vous avez un gestionnaire de fonction normal qui a besoin d'accéder à l'instance actuelle de la fonction class via this :

myHandler(){
  //  this.setState(...)
}

Vous devrez expliciter bind à la class .
L'approche la plus courante consistera à le faire dans la section constructor car il ne fonctionne qu'une seule fois :

constructor(props){
  super(props);
  this.myHandler = this.myHandler.bind(this);
}

Si vous utilisez une fonction flèche en tant que gestionnaire, vous n'avez pas besoin de bind à la class car comme mentionné ci-dessus, la fonction flèche utilise un contexte lexical pour this :

myHandler = () => {
  //  this.setState(...)
}

Dans les deux cas, vous utiliserez le gestionnaire de la manière suivante :

<div onClick={this.myHandler}></div> 

La principale raison de cette approche :

<div onClick={() => this.myHandler(someParameter)}></div>

C'est si vous voulez passer des paramètres au gestionnaire en plus de la fonction native event qui sont passés, ce qui signifie que vous voulez passer un paramètre vers le haut.

Comme mentionné, cela créera une nouvelle instance de fonction à chaque rendu.
(Il existe une meilleure approche pour cela, continuez à lire).

Exemple de fonctionnement pour un tel cas d'utilisation :

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      items: [{ name: 'item 1', active: false }, { name: 'item 2', active: true }],
    }
  }
  toggleITem = (itemName) => {
    this.setState(prev => {
      const nextState = prev.items.map(item => {
        if (item.name !== itemName) return item;
        return {
          ...item,
          active: !item.active
        }
      });
      return { items: nextState };
    });
  }
  render() {
    const { items } = this.state;
    return (
      <div>
        {
          items.map(item => {
            const style = { color: item.active ? 'green' : 'red' };
            return (
              <div
                onClick={() => this.toggleITem(item.name)}
                style={style}
              >
                {item.name}
              </div>

          )})
        }
      </div>
    );
  }
}

ReactDOM.render(<App />, document.getElementById('root'));

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>

Une meilleure approche consisterait à créer une composition de composants.
Vous pouvez créer un composant enfant qui englobe le balisage pertinent, qui aura son propre gestionnaire et qui obtiendra à la fois l'adresse de l'utilisateur et celle de l'utilisateur. data et handler comme des accessoires du parent.

Le composant enfant invoquera alors le gestionnaire qu'il a obtenu du parent et transmettra le paramètre data en tant que paramètre.

Exemple de fonctionnement avec le composant enfant :

class Item extends React.Component {
  onClick = () => {
    const { onClick, name } = this.props;
    onClick(name);
  }
  render() {
    const { name, active } = this.props;
    const style = { color: active ? 'green' : 'red' };
    return (<div style={style} onClick={this.onClick}>{name}</div>)
  }
}

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      items: [{ name: 'item 1', active: false }, { name: 'item 2', active: true }],
    }
  }
  toggleITem = (itemName) => {
    this.setState(prev => {
      const nextState = prev.items.map(item => {
        if (item.name !== itemName) return item;
        return {
          ...item,
          active: !item.active
        }
      });
      return { items: nextState };
    });
  }
  render() {
    const { items } = this.state;
    return (
      <div>
        {
          items.map(item => {
            return <Item {...item} onClick={this.toggleITem} />
          })
        }
      </div>
    );
  }
}

ReactDOM.render(<App />, document.getElementById('root'));

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>

Les champs de la classe côté négatif :
Comme je l'ai mentionné, il y a un petit inconvénient pour les champs de classe.
La différence entre une méthode de classe et un champ de classe est que le champ de classe est attaché à l'élément instance de la class (fonction constructeur).
alors que les méthodes et les objets de la classe sont attachés au prototype.

Par conséquent, si vous avez un nombre ridiculement important d'instances de cette classe, vous mai obtenir un coup de performance.

Étant donné ce bloc de code :

class MyClass {
  myMethod(){}  
  myOtherMethod = () => {}
}

babel le transposera en ceci :

var _createClass = function() {
  function defineProperties(target, props) {
    for (var i = 0; i < props.length; i++) {
      var descriptor = props[i];
      descriptor.enumerable = descriptor.enumerable || false;
      descriptor.configurable = true;
      if ("value" in descriptor) descriptor.writable = true;
      Object.defineProperty(target, descriptor.key, descriptor);
    }
  }
  return function(Constructor, protoProps, staticProps) {
    if (protoProps) defineProperties(Constructor.prototype, protoProps);
    if (staticProps) defineProperties(Constructor, staticProps);
    return Constructor;
  };
}();

function _classCallCheck(instance, Constructor) {
  if (!(instance instanceof Constructor)) {
    throw new TypeError("Cannot call a class as a function");
  }
}

var MyClass = function() {
  function MyClass() {
    _classCallCheck(this, MyClass);

    this.myOtherMethod = function() {};
  }

  _createClass(MyClass, [{
    key: "myMethod",
    value: function myMethod() {}
  }]);

  return MyClass;
}();

0 votes

React dit que ce n'est pas vrai (vos mots sur le 1er exemple - 'créer une nouvelle instance de fonction à chaque rendu' ) reactjs.org/docs/faq-functions.html

0 votes

@diesel94 Êtes-vous sûr ? Ce que j'ai lu dans le lien que vous avez posté est à peu près exactement ce que j'ai écrit : "L'utilisation d'une fonction fléchée dans le rendu crée une nouvelle fonction à chaque fois que le composant est rendu, ce qui peut compromettre les optimisations basées sur une comparaison stricte des identités.

67voto

Shubham Khatri Points 67350

Je comprends que les fonctions de flèche rendent les choses plus efficaces en ne recréant pas en ne recréant pas les fonctions à chaque rendu, de la même manière que la liaison dans le fonctionne.

Ce n'est pas vrai. Cela dépend de l'endroit exact où vous utilisez la fonction Flèche. Si Arrow function sont utilisés dans la méthode de rendu, alors ils créent une nouvelle instance everytime est appelé de la même manière que bind fonctionnerait. Prenons l'exemple suivant

<div onClick={()=>{this.onClick()}}>Previous</div>

Ici, chaque fois que render est appelé, une fonction anonyme est créée et cette fonction, lorsqu'elle est appelée, appelle this.onClick .

Cependant, considérez le cas ci-dessous

onClick = () => {
    console.log("Div is clicked")
}

Dans le cas ci-dessus, la fonction flèche ne recrée pas la fonction à chaque fois, mais lie le contexte au composant React en tant que An arrow function does not have its own this; the this value of the enclosing execution context is used. une fois lorsque la classe est instanciée. Ceci est similaire à la façon dont binding works is constructor . Il s'agit d'une partie de proposed class fields for arrow functions et ce n'est pas une fonctionnalité de l'ES6,

Pour comprendre ce que vous voulez demander, vous devez savoir qu'une fonction reçoit son contexte de l'endroit où elle est appelée. Consultez this question pour mieux comprendre.

Dans votre cas, vous avez utilisé Arrow function pour définir prevItem et donc il obtient le contexte du composant React qui l'entoure.

prevItem = () => {
    console.log("Div is clicked")
}

render(){
    return (
         <SecondClass prevItem={this.prevItem} />
    )
}

Maintenant dans son enfant, même si vous appelez prevItem avec n'importe quel contexte personnalisé, using bind or arrow function , prevItem lorsqu'il est exécuté dans le parent, c'est-à-dire Main.js obtiendra le contexte du composant React qui l'entoure. Et puisque vous souhaitez simplement exécuter la fonction prevItem et que vous ne voulez pas lui transmettre de données depuis l'enfant, il suffit d'écrire

<ThirdClass type="prev" onClick={()=>this.props.prevItem()} />

et

<div onClick={()=>{this.props.onClick()}}>Previous</div>

est tout simplement inutile et ne fera qu'ajouter à l'implication de la performance puisque de nouvelles fonctions sont créées dans SecondClass et ThirdClass à chaque fois. Vous n'avez tout simplement pas besoin de définir ces fonctions comme des fonctions de flèche et vous pouvez simplement écrire

<ThirdClass type="prev" onClick={this.props.prevItem} />

et

<div onClick={this.props.onClick}>Previous</div>

car il est déjà lié au parent.

Maintenant, même si vous devez passer des données supplémentaires à ces fonctions de la ThirdClass et de la SecondClass, vous ne devez pas utiliser directement la fonction Arrow function ou bind in render . Jetez un coup d'œil à cette réponse sur How to Avoid binding in Render method

6 votes

L'utilisation des fonctions de flèche n'est pas mauvaise en soi. Il est tout à fait possible d'utiliser des fonctions fléchées dans render même s'ils sont recréés. Dans la plupart des applications, la différence de perforation ne sera pas perceptible.

0 votes

@DivyanshuMaithani Est-ce que le code ci-dessus fonctionnera pour les composants fonctionnels aussi bien ou son seulement pour le composant de classe, avons-nous besoin de le lier à nouveau comme ci-dessous lors de l'utilisation du composant fonctionnel SecondClass.js <ThirdClass type="prev" onClick={()=>this.props.prevItem()}. /> ThirdClass.js <div onClick={()=>{this.props.onClick()}}>Précédent</div>

0 votes

Mon commentaire concernait les problèmes de performance. La réponse est un peu confuse, en bref, vous devriez seulement avoir besoin de lier une méthode de classe (dans les composants de classe). Il n'est pas nécessaire de lier une fonction pour les composants fonctionnels, car vous ne seriez pas concerné par le fait que this l'usage.

5voto

simbathesailor Points 2334

Donc votre première approche

<ThirdClass type="prev" onClick={()=>this.props.prevItem()} />

Dans ce cas, vous pouvez passer n'importe quel argument disponible dans ThirdClass à la fonction prevItem. C'est une bonne façon d'appeler les fonctions parentales avec des arguments.

<ThirdClass type="prev" onClick={()=>this.props.prevItem(firstArgument, secondArgument)} />

Votre deuxième approche est

<ThirdClass type="prev" onClick={this.props.prevItem} />

Cette approche ne vous permet pas de passer des arguments spécifiques à la ThirdClass.

Les deux approches sont correctes, mais cela dépend de votre cas d'utilisation. cas. Les deux approches utilisant la fonction flèche es6 sont correctes dans les scénarios respectifs mentionnés ci-dessus. mentionnés ci-dessus

5voto

AmerllicA Points 1

Utilisation de JavaScript déclaration de fonction curring, peut être une manière différente à d'autres réponses, faites attention aux codes suivants :

clickHandler = someData => e => this.setState({
  stateKey: someData
});

Maintenant dans JSX vous pouvez écrire :

<div onClick={this.clickHandler('someData')} />

Le site clickHandler avec someData retourner une fonction avec e mais il n'est pas utilisé à l'intérieur de clickHandler donc cela fonctionne bien.

Pour écrire plus complètement, écrivez comme ci-dessous :

clickHandler = someData => () => this.setState({
  stateKey: someData
});

Il n'est pas nécessaire de e alors pourquoi je devrais l'écrire.

2voto

Josh Pittman Points 1504

L'utilisation de flèches dans votre définition de fonction originale vous permet de ne pas lier la fonction dans votre constructeur.

Si tu n'as pas utilisé de flèche...

prevItem(){
  console.log("Div is clicked")
}

Il faudrait alors créer un constructeur et le lier à cet endroit...

class MyComponent extends Component {
  constructor(props) {
    super(props)
    this.prevItem = this.prevItem.bind(this)
  }

  prevItem() { ... }
}

L'utilisation de la flèche est plus facile au début car elle fonctionne tout simplement et vous n'avez pas besoin de comprendre ce qu'est un constructeur et de vous plonger dans la complexité de this en javascript.

Toutefois, en termes de performances, il est préférable de lier les données dans le constructeur. La méthode bind in constructor créera une seule instance de la fonction et la réutilisera, même si la méthode render est appelée plusieurs fois.

0 votes

Avec cette approche, existe-t-il un moyen d'ajouter un paramètre lors de l'appel de prevItem. ex : onpress={this.prevItem} mais j'aimerais appeler this.prevItem(variable)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X