Utilisation correcte des fonctions de flèche dans React

je suis en utilisant ReactJS avec Babel et Webpack et à l'aide de ES6 ainsi que l' champs de classe proposés pour les fonctions de flèche. Je comprends que les fonctions de flèche rendent les choses plus efficaces par ne pas recréer les fonctions de chaque Rendu similaire au fonctionnement de la reliure dans le constructeur. Cependant, je ne suis pas sûr à 100% de les utiliser correctement. Ce qui suit est une coupe 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 également pu 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 de flèche ES6 dans ma définition de fonction originale? Ou devrais-je utiliser la syntaxe de la flèche tout le chemin jusqu'à mon dernier div?

17
demandé sur kojow7 2018-02-09 08:55:40

4 réponses

je comprends que la flèche fonctions de rendre les choses plus efficaces en a pas recréer les fonctions rend chacune similaire à la façon dont la liaison dans le constructeur œuvres.

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 render, puis ils créent une nouvelle instance everytime render est appelé commebind va fonctionner. Considérons cet exemple

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

Ici chaque fois que le rendu est appelé une fonction anonyme est créé et cette fonction quand appelé, appelle this.onClick.

Toutefois de considérer 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 comme 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. C'est une partie de proposed class fields for arrow functions et ce n'est pas un ES6 fonctionnalité,

pour comprendre ce que vous voulez demander, vous devez savoir qu'une fonction est son contexte d'où il est appelé. Vérifier this question pour plus de compréhension.

Dans votre cas, vous avez utilisé Arrow function définir prevItem et donc il obtient le contexte du composant de réaction d'enclos.

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 parent I. e Main.js va obtenir le contexte de son composant de réaction d'enclos. Et puisque vous voulez simplement exécutez la fonction prevItem et ne voulez pas transmettre de données à celle-ci de la part de l'enfant, en écrivant

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

et

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

est tout simplement inutile et ne fera qu'Ajouter aux implications de performance puisque de nouvelles fonctions sont créées dans SecondClass et ThirdClass à chaque fois. Vous n'avez tout simplement pas besoin d'avoir ces fonctions définies comme une fonction de flèche et vous pouvez simplement écrire

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

et

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

puisqu'il est déjà fixé dans le parent.

maintenant même si vous devez passer quelques données supplémentaires à ces fonctions de ThirdClass et SecondClass, vous ne devriez pas utiliser directement Arrow function ou bind in render. Jetez un oeil à cette réponse sur How to Avoid binding in Render method

11
répondu Shubham Khatri 2018-02-12 06:50:56

je comprends que les fonctions de flèche rendent les choses plus efficaces en ne recréer les fonctions à chaque fois qu'ils sont appelés

C'est true.

les fonctions Arrow gèrent le this dans un contexte lexical de manière "normale" est la fonction dynamiquement.

sur vos deux exemples de la fonction flèche en ligne, vous créez une nouvelle instance de fonction render.

Cela créera et passera une nouvelle instance sur chaque Rendu

onClick={() => {}}

dans le troisième exemple, vous n'avez qu'une seule instance.

Cela ne passer une référence à une instance de

onClick={this.myHandler}


Quant aux avantages des fonctions de flèche en tant que champs de classe (il y a un petite mise de côté, je le posterai au bas de la réponse), si vous avez un gestionnaire de fonction normal qui doit accédez à l'instance courante du class par this:
myHandler(){
  //  this.setState(...)
}

Vous devez explicite bind la class.

L'approche la plus courante sera de le faire dans le constructor parce qu'il s'exécute une seule fois:

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

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

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

Avec les deux les approches que vous allez utiliser le gestionnaire comme ceci:

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

La raison principale de cette approche:

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

si vous voulez passer des paramètres au gestionnaire de côté le natif 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 sur chaque Rendu.

(Il y est une meilleure approche pour cela, continuez à lire).

exemple pour ces 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 serait de créer la composition des composants.

Vous pouvez créer un composant enfant qui encapsule la balise pertinente, aura son propre gestionnaire et obtenir à la fois l' data et handler comme accessoire du parent.

le composant enfant va alors invoquer le handler qu'il a reçu du parent et passer le data comme paramètre.

exemple D'exécution avec enfant composant:

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>

Champs de la Classe de l' -:

Comme je l'ai mentionné, il y a un petit côté 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é au instanceclass (fonction constructeur).

où comme les méthodes de classe et les objets sont attachés à prototype.

Donc, si vous avez ridiculement grande quantité d'instances de cette classe vous obtenir un résultat de performance.

étant donné ce bloc de code:

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

babel Le transposera à 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;
}();
26
répondu Sagiv b.g 2018-03-28 18:47:24

donc votre première approche

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

vous pouvez passer tous les arguments disponibles dans ThirdClass à la fonction prevItem. C'est la bonne façon d'appeler les fonctions parent avec des arguments.Comme ceci

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

Votre deuxième approche est

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

cette approche vous interdit de passer tous les arguments spécifiques de ThirdClass.

les deux répartitions sont correctes, c'est juste que, cela dépend de votre utilisation cas. Les deux approche à l'aide de la fonction Flèche es6 et sont à droite dans le mentionné ci-dessus scénarios correspondants

4
répondu stack26 2018-02-09 06:29:00

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

si vous n'avez pas utilisé de flèche...

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

alors vous devez créer un constructeur pour l'y lier...

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

  prevItem() { ... }
}

utiliser la flèche est plus facile quand vous commencez à propos parce que cela fonctionne Juste et vous n'avez pas à comprendre ce qu'est un constructeur et plonger dans les complexités de this en javascript.

Cependant, du point de vue des performances, il est préférable de lier le constructeur. La méthode bind dans le constructeur va créer une instance unique de la fonction et la réutiliser, même si la méthode render est appelée plusieurs fois.

1
répondu Josh Pittman 2018-02-09 06:22:26