React: "this" non è definito all'interno di una funzione componente


152
class PlayerControls extends React.Component {
  constructor(props) {
    super(props)

    this.state = {
      loopActive: false,
      shuffleActive: false,
    }
  }

  render() {
    var shuffleClassName = this.state.toggleActive ? "player-control-icon active" : "player-control-icon"

    return (
      <div className="player-controls">
        <FontAwesome
          className="player-control-icon"
          name='refresh'
          onClick={this.onToggleLoop}
          spin={this.state.loopActive}
        />
        <FontAwesome
          className={shuffleClassName}
          name='random'
          onClick={this.onToggleShuffle}
        />
      </div>
    );
  }

  onToggleLoop(event) {
    // "this is undefined??" <--- here
    this.setState({loopActive: !this.state.loopActive})
    this.props.onToggleLoop()
  }

Voglio aggiornare lo loopActivestato su Attiva / Disattiva, ma l' thisoggetto non è definito nel gestore. Secondo il doc tutorial, thisdovrei fare riferimento al componente. Mi sto perdendo qualcosa?

Risposte:


211

ES6 React.Component non associa automaticamente i metodi a se stesso. Devi vincolarli tu stesso nel costruttore. Come questo:

constructor (props){
  super(props);

  this.state = {
      loopActive: false,
      shuffleActive: false,
    };

  this.onToggleLoop = this.onToggleLoop.bind(this);

}

23
se cambi la tua proprietà onClick () => this.onToggleLoopdopo aver spostato la funzione onToggleLoop nella tua classe di reazione, funzionerà anche.
Sam,

71
Devi davvero vincolare tutti i metodi di ogni classe di reazione? Non è un po 'folle?
Alex L

6
@AlexL Ci sono modi per farlo senza legare esplicitamente i metodi. se usi babel è possibile dichiarare tutti i metodi sul componente React come funzioni freccia. Ci sono esempi qui: babeljs.io/blog/2015/06/07/react-on-es6-plus
Ivan

7
Ma perché thisnon è definito in primo luogo? So che thisin Javascript dipende da come viene chiamata la funzione, ma cosa sta succedendo qui?
Maverick,

1
ma come posso fare questo è la funzione non è definita fino a dopo il costruttore? Ottengo un "Impossibile leggere la proprietà 'bind' di undefined" se provo a farlo nel costruttore anche se la mia funzione è definita sulla classe :(
rex

87

Ci sono un paio di modi.

Uno è quello di aggiungere this.onToggleLoop = this.onToggleLoop.bind(this);nel costruttore.

Un altro è le funzioni freccia onToggleLoop = (event) => {...}.

E poi c'è onClick={this.onToggleLoop.bind(this)}.


1
Perché onToogleLoop = () => {} funziona? Ho avuto lo stesso problema e lo rilevo nel mio costruttore ma non ha funzionato ... e ora ho visto il tuo post e ho sostituito il mio metodo con una sintassi della funzione freccia e funziona. Puoi spiegarmelo?
Guchelkaben,

5
da developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… ; Una funzione freccia non crea questo proprio, viene utilizzato questo valore del contesto di esecuzione allegato.
J. Mark Stevens,

1
Si noti che il collegamento inline in onClickrestituirà una nuova funzione ogni rendering e quindi sembra che sia stato passato un nuovo valore per il prop, facendo casino con shouldComponentUpdatein PureComponents.
Ninjakannon,

24

Scrivi la tua funzione in questo modo:

onToggleLoop = (event) => {
    this.setState({loopActive: !this.state.loopActive})
    this.props.onToggleLoop()
}

Funzioni freccia grassa

l'associazione per la parola chiave è la stessa all'esterno e all'interno della funzione freccia grassa. Ciò è diverso dalle funzioni dichiarate con la funzione, che può associarlo a un altro oggetto al momento dell'invocazione. Mantenere questa associazione è molto conveniente per operazioni come la mappatura: this.items.map (x => this.doSomethingWith (x)).


Se lo faccio ottengo ReferenceError: fields are not currently supported.
Pavel Komarov,

Funziona se all'interno del costruttore dico this.func = () => {...}, ma lo considero un tipo sciocco e voglio evitarlo se possibile.
Pavel Komarov,

Così terribile che non puoi usare la normale sintassi della classe in React!
Kokodoko,

11

Mi sono imbattuto in un legame simile in una funzione di rendering e ho finito per passare il contesto thisnel modo seguente:

{someList.map(function(listItem) {
  // your code
}, this)}

Ho anche usato:

{someList.map((listItem, index) =>
    <div onClick={this.someFunction.bind(this, listItem)} />
)}

Ci sono molte funzioni non necessarie che stai creando lì, ogni volta che viene visualizzato l'elenco ...
TJ Crowder,

@TJCrowder Sì, è vero che queste funzioni vengono create di nuovo ogni volta che viene chiamato il rendering. È meglio creare le funzioni come metodi di classe e associarle una volta alla classe, ma per i principianti l'associazione manuale al contesto può essere utile
duhaime

2

Dovresti notare che thisdipende da come viene invocata la funzione, ovvero: quando una funzione viene chiamata come metodo di un oggetto, thisviene impostata sull'oggetto su cui viene chiamato il metodo.

thisè accessibile nel contesto JSX come oggetto componente, quindi è possibile chiamare il metodo desiderato in linea come thismetodo.

Se passi semplicemente il riferimento a funzione / metodo, sembra che la reazione lo invocherà come funzione indipendente.

onClick={this.onToggleLoop} // Here you just passing reference, React will invoke it as independent function and this will be undefined

onClick={()=>this.onToggleLoop()} // Here you invoking your desired function as method of this, and this in that function will be set to object from that function is called ie: your component object

1
Bene, puoi persino usare la prima riga, cioè a onClick={this.onToggleLoop}condizione che nella tua classe di componenti tu abbia definito un campo (proprietà)onToggleLoop = () => /*body using 'this'*/
gvlax,

1

Se stai usando babel, associ 'questo' usando l'operatore di binding ES7 https://babeljs.io/docs/en/babel-plugin-transform-function-bind#auto-self-binding

export default class SignupPage extends React.Component {
  constructor(props) {
    super(props);
  }

  handleSubmit(e) {
    e.preventDefault(); 

    const data = { 
      email: this.refs.email.value,
    } 
  }

  render() {

    const {errors} = this.props;

    return (
      <div className="view-container registrations new">
        <main>
          <form id="sign_up_form" onSubmit={::this.handleSubmit}>
            <div className="field">
              <input ref="email" id="user_email" type="email" placeholder="Email"  />
            </div>
            <div className="field">
              <input ref="password" id="user_password" type="new-password" placeholder="Password"  />
            </div>
            <button type="submit">Sign up</button>
          </form>
        </main>
      </div>
    )
  }

}

0

Se chiami il tuo metodo creato nei metodi del ciclo di vita come componentDidMount ... allora puoi usare solo la funzione this.onToggleLoop = this.onToogleLoop.bind(this)e fat arrow onToggleLoop = (event) => {...}.

L'approccio normale alla dichiarazione di una funzione nel costruttore non funzionerà perché i metodi del ciclo di vita sono chiamati in precedenza.


0

nel mio caso questa era la soluzione = () => {}

methodName = (params) => {
//your code here with this.something
}

0

Nel mio caso, per un componente apolide che ha ricevuto il riferimento con forwardRef, ho dovuto fare ciò che viene detto qui https://itnext.io/reusing-the-ref-from-forwardref-with-react-hooks-4ce9df693dd

Da questo (onClick non ha accesso all'equivalente di 'this')

const Com = forwardRef((props, ref) => {
  return <input ref={ref} onClick={() => {console.log(ref.current} } />
})

A questo (funziona)

const useCombinedRefs = (...refs) => {
  const targetRef = React.useRef()

  useEffect(() => {
    refs.forEach(ref => {
      if (!ref) return

      if (typeof ref === 'function') ref(targetRef.current)
      else ref.current = targetRef.current
    })
  }, [refs])

  return targetRef
}

const Com = forwardRef((props, ref) => {
  const innerRef = useRef()
  const combinedRef = useCombinedRefs(ref, innerRef)

  return <input ref={combinedRef } onClick={() => {console.log(combinedRef .current} } />
})
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.