Impossible d'accéder à l'instance React (ceci) dans le gestionnaire d'événements intérieur

J'écris un composant simple dans ES6 (avec BabelJS), et les fonctions this.setState ne fonctionnent pas.

Les erreurs typiques incluent quelque chose comme

Impossible de lire la propriété 'setState' de undefined

ou

This.setState n'est pas une fonction

Est-ce que tu sais pourquoi? Voici le code:

 import React from 'react' class SomeClass extends React.Component { constructor(props) { super(props) this.state = {inputContent: 'startValue'} } sendContent(e) { console.log('sending input content '+React.findDOMNode(React.refs.someref).value) } changeContent(e) { this.setState({inputContent: e.target.value}) } render() { return ( <div> <h4>The input form is here:</h4> Title: <input type="text" ref="someref" value={this.inputContent} onChange={this.changeContent} /> <button onClick={this.sendContent}>Submit</button> </div> ) } } export default SomeClass 

this.changeContent doit être lié à l'instance de composant via this.changeContent.bind(this) avant d'être transmis en tant que support onChange , sinon this variable dans le corps de la fonction ne se référera pas à l'instance de composant mais à la window . Voir Fonction :: bind .

Lors de l'utilisation de React.createClass place des classes ES6, toutes les méthodes non-cycle de vie définies sur un composant sont automatiquement liées à l'instance de composant. Voir Autobinding .

Soyez conscient que la liaison d'une fonction crée une nouvelle fonction. Vous pouvez soit le lier directement en mode rendu, ce qui signifie qu'une nouvelle fonction sera créée chaque fois que le composant le rendra ou le lier dans votre constructeur, qui ne démarrera qu'une fois.

 constructor() { this.changeContent = this.changeContent.bind(this); } 

contre

 render() { return <input onChange={this.changeContent.bind(this)} />; } 

Les références sont définies sur l'instance de composant et non sur React.refs : vous devez modifier React.refs.someref sur this.refs.someref . Vous devrez également lier la méthode sendContent à l'instance du composant afin qu'elle se réfère à elle.

Morhaus est correct, mais cela peut être résolu sans bind .

Vous pouvez utiliser une fonction de flèche avec la proposition de propriétés de classe :

 class SomeClass extends React.Component { changeContent = (e) => { this.setState({inputContent: e.target.value}) } render() { return <input type="text" onChange={this.changeContent} />; } } 

Parce que la fonction flèche est déclarée dans la portée du constructeur, et comme les fonctions fléchées les conservent à partir de leur portée déclarante, tout fonctionne. L'inconvénient ici est que ces fonctions ne seront pas sur le prototype, elles seront toutes recréées avec chaque composant. Cependant, ce n'est pas un inconvénient puisque bind résulte de la même chose.

Ce problème est l'une des premières choses que la plupart d'entre nous expérimentons, lors de la transition de la syntaxe de définition de composant React.createClass() à la classe ES6 d'extension React.Component .

C'est le résultat de this contexte de différences dans React.createClass() vs extends React.Component .

L'utilisation de React.createClass() liera automatiquement this contexte (valeurs) correctement, mais ce n'est pas le cas lors de l'utilisation des classes ES6. Lorsque vous le faites de manière ES6 (en étendant React.Component ), this contexte est null par défaut. Les propriétés de la classe ne se lient pas automatiquement à l'instance de la classe React (composant).

En savoir plus sur les différences de syntaxe dans les exemples dans ReactJS docs ici .


Approches pour résoudre ce problème

Je connais un total de 4 approches générales.

  1. Reliez vos fonctions dans le constructeur de classe . Considéré par beaucoup comme une approche de la meilleure pratique qui évite de toucher JSX du tout et ne crée pas une nouvelle fonction sur chaque composant re-render.

     class SomeClass extends React.Component { constructor(props) { super(props); this.handleClick = this.handleClick.bind(this); } handleClick() { console.log(this); // the React Component instance } render() { return ( <button onClick={this.handleClick}></button> ); } } 
  2. Reliez vos fonctions en ligne . Vous pouvez toujours trouver cette approche utilisée ici et là dans certains tutoriels / articles / etc, il est donc important que vous soyez conscient de cela. C'est le même concept comme # 1, mais soyez conscient que la liaison d'une fonction crée une nouvelle fonction par chaque re-rendu.

     class SomeClass extends React.Component { handleClick() { console.log(this); // the React Component instance } render() { return ( <button onClick={this.handleClick.bind(this)}></button> ); } } 
  3. Utilisez une fonction de flèche en gras . Jusqu'à ce que la flèche fonctionne, chaque nouvelle fonction définit sa propre valeur. Cependant, la fonction flèche ne crée pas son propre contexte, donc this a la signification originale de l'instance du composant React. Par conséquent, nous pouvons:

     class SomeClass extends React.Component { handleClick() { console.log(this); // the React Component instance } render() { return ( <button onClick={ () => this.handleClick() }></button> ); } } 

    ou

     class SomeClass extends React.Component { handleClick = () => { console.log(this); // the React Component instance } render() { return ( <button onClick={this.handleClick}></button> ); } } 
  4. Utilisez la bibliothèque de fonctions utilitaires pour lier automatiquement vos fonctions . Il existe quelques librairies d'utilité, qui effectue automatiquement le travail pour vous. Voici quelques-uns des plus populaires, pour n'en citer que quelques-uns:

    • Autobind Decorator est un paquet NPM qui lie les méthodes d'une classe à l'instance correcte, même si les méthodes sont détachées. Le package utilise @autobind avant les méthodes pour lier this à la référence correcte au contexte du composant.

       import autobind from 'autobind-decorator'; class SomeClass extends React.Component { @autobind handleClick() { console.log(this); // the React Component instance } render() { return ( <button onClick={this.handleClick}></button> ); } } 

      Autobind Decorator est assez intelligent pour nous lier toutes les méthodes dans une classe de composant à la fois, tout comme l'approche n ° 1.

    • Class Autobind est un autre paquetage NPM largement utilisé pour résoudre ce problème de liaison. Contrairement à Autobind Decorator, il n'utilise pas le motif du décorateur, mais il suffit d'utiliser une fonction dans votre constructeur qui lie automatiquement les méthodes du composant à la référence correcte de this .

       import autobind from 'class-autobind'; class SomeClass extends React.Component { constructor() { autobind(this); // or if you want to bind only only select functions: // autobind(this, 'handleClick'); } handleClick() { console.log(this); // the React Component instance } render() { return ( <button onClick={this.handleClick}></button> ); } } 

      PS: Une autre bibliothèque très similaire est React Autobind .


Recommandation

Si j'étais vous, je m'en tiendrais à l'approche # 1. Cependant, dès que vous obtenez une tonne de liens dans votre constructeur de classe, je vous recommanderais d'explorer une des bibliothèques helper mentionnées dans l'approche n ° 4.


Autre

Ce n'est pas lié au problème que vous avez, mais vous ne devriez pas abuser des références .

Votre première inclinaison peut être d'utiliser les refs pour "faire avancer les choses" dans votre application. Si tel est le cas, prenez un moment et pensez plus sérieusement à l'endroit où l'État devrait appartenir à la hiérarchie des composants.

Pour des raisons similaires, tout comme celui dont vous avez besoin, l'utilisation d'un composant contrôlé est la manière privilégiée. Je vous suggère d'envisager d'utiliser votre state Component . Ainsi, vous pouvez simplement accéder à la valeur comme ceci: this.state.inputContent .

Ce problème se produit car this.changeContent et onClick={this.sendContent} ne sont pas liés à ceci de l'instance du composant.

Il existe une autre solution (en plus d'utiliser bind () dans le constructeur ()) pour utiliser les fonctions de flèche de ES6 qui partagent la même portée lexicale du code environnant et que vous maintenez ceci , afin que vous puissiez modifier votre code en render () à être :

 render() { return ( <input type="text" onChange={ () => this.changeContent() } /> <button onClick={ () => this.sendContent() }>Submit</button> ) } 

Bonjour, si vous voulez ne pas vous lier à votre appel de fonction. Vous pouvez utiliser 'class-busind' et l'importer comme ça

 import autobind from 'class-autobind'; class test extends Component { constructor(props){ super(props); autobind(this); } 

N'écris pas autobind avant le super-appel parce qu'il ne fonctionnera pas

Dans le cas où vous souhaitez conserver la liaison dans la syntaxe du constructeur, vous pouvez utiliser l' opérateur de proposition-bind et transformer votre code comme suit:

 constructor() { this.changeContent = ::this.changeContent; } 

Au lieu de :

 constructor() { this.changeContent = this.changeContent.bind(this); } 

Beaucoup plus simple, pas besoin de bind(this) ou fatArrow .