Immaginiamo ora di voler settare una qualche proprietà dello stato al click del bottone. L’esempio sopra diventa:
class App extends React.Component { constructor(props) { super(props); this.state = {event: '-'}; } handleClick(e) { this.setState({event: e.type}); } render() { return ( <div> <p>Tipo di evento: {this.state.event}</p> {/* Errore! Non fatelo a casa :) */} <button onClick={this.handleClick} >Pulsante</button> </div> ) } }
Nella console apparirà un messaggio di errore “Cannot read property ‘setState’ of null“:
Il riferimento “this” è null. Questo tipo di errore non è legato a React, ma è Javascript. Per trovare la causa dell’errore facciamo qualche esempio in Javascript.
"use strict"; function foo(){ return this; } console.log(foo() === undefined); // true class Baz { constructor() { console.log("Istanza di Baz creata"); this.prop = "Baz prop"; } bazFunc() { console.log(this.prop); } receiveCallback(callback) { console.log("receiveCallback called"); // invochiamo la callback ricevuta come parametro callback(); } } class Bat { constructor() { console.log("Istanza di Bat creata"); this.prop = "Bat prop"; } batFunc() { console.log(this.prop); } } const baz = new Baz(); const bat = new Bat(); baz.bazFunc() // "Baz prop" bat.batFunc() // "Bat prop" // 'this' sarà undefined baz.receiveCallback(bat.batFunc); // Errore!
Notiamo che, quando invochiamo la funzione foo(), this sarà pari a ‘undefined’. Inoltre, nell’ultimo rigo, quando chiamiamo baz.receiveCallback() e passiamo un riferimento al metodo bat.batFunc, riceveremo il seguente errore nella console degli strumenti per sviluppatori.
Ossia, quando invochiamo callback(), ed eseguiamo il corpo della funzione batFunc(), this è ‘undefined’. Questo perche, il valore di this è determinato da come la funzione viene invocata, non è come java in cui this è legato alla classe in cui è definita la funzione.
Per “legare” this al contesto in cui è definito il metodo, similmente a java, dobbiamo usare le arrow function.
Riscrivendo l’esempio precedente avremo:
"use strict"; const globalObject = this; const foo = () => this; // globalObject === window nel browser console.log(foo() === globalObject); // true class Baz { constructor(bat) { this.prop = "Baz prop"; this.bat = bat; } receiveCallback(callback) { const callbackResult = callback(); console.log( "this.bat === callbackResult? ", this.bat === callbackResult ); // true nel caso dell'arrow function } } class Bat { constructor() { this.prop = "Bat prop"; } normalFunc() { return function() { console.log(this.prop); } } arrowFunc() { return () => { console.dir("L'arrow function restituita da arrowFunc() ha accesso a: " + this.prop); return this; } } } const bat = new Bat(); const baz = new Baz(bat); // NOTA: stiamo invocando la funzione arrowFunc() // 'this' punta all'oggetto bat baz.receiveCallback(bat.arrowFunc()); // "Bat prop" // NOTA: stiamo invocando la funzione normalFunc() // 'this' sarà undefined baz.receiveCallback(bat.normalFunc()); // Error! Cannot read property 'prop' of undefined
Ritornando su React, potremmo quindi usare delle arrow function come event handler all’interno dei nostri componenti
class App extends React.Component { constructor(props) { super(props); this.state = {event: '-'}; } handleClick(e) { this.setState({event: e.type}); } render() { return ( <div> <p>Tipo di evento: {this.state.event}</p> <button onClick={(e) => this.handleClick(e)} >Pulsante</button> </div> ) } }
Con questa sintassi però viene creata una callback diversa ad ogni render. Nella maggior parte dei casi, non vi sono problemi. Tuttavia, se questa callback viene passata come prop a componenti inferiori, tali componenti potrebbero eseguire un ulteriore re-renderizzamento, causando un problema di prestazioni. Per risolvere tutte queste problematiche, si usa la funzione Function.prototype.bind().
Esempio:
bFoo = foo.bind(newThis); bFoo()
bFoo, grazie alla funzione Function.prototype.bind, sarà una copia di foo, ma il valore di this, all’interno di bFoo, sarà uguale al valore di newThis (il primo argomento della funzione bind.
Ritornando al nostro esempio, all’interno del costruttore useremo Function.prototype.bind sulla funzione this.handleClick
class App extends React.Component { constructor(props) { super(props); this.state = {event: '-'}; // usiamo Function.prototype.bind() all'interno del costruttore this.handleClick = this.handleClick.bind(this); } handleClick(e) { this.setState({event: e.type}); } render() { return ( <div> <p>Tipo di evento: {this.state.event}</p> {/* Passiamo un riferimento alla funzione this.handleClick */} <button onClick={this.handleClick} >Pulsante</button> </div> ) } }