Reaktion: "dies" ist innerhalb einer Komponentenfunktion undefiniert

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()
  }

Ich möchte den loopActiveStatus beim Umschalten aktualisieren , aber das thisObjekt ist im Handler nicht definiert. Laut Tutorial-Dokument sollte ich mich thisauf die Komponente beziehen. Vermisse ich etwas

Maximus S.
quelle

Antworten:

211

ES6 React.Component bindet Methoden nicht automatisch an sich selbst. Sie müssen sie selbst im Konstruktor binden. So was:

constructor (props){
  super(props);

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

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

}
Ivan
quelle
23
Wenn Sie Ihre onClick-Eigenschaft ändern, () => this.onToggleLoopnachdem Sie die onToggleLoop-Funktion in Ihre React-Klasse verschoben haben, funktioniert dies ebenfalls.
Sam
71
Müssen Sie wirklich jede Methode jeder Reaktionsklasse binden? Ist das nicht ein bisschen verrückt?
Alex L
6
@AlexL Es gibt Möglichkeiten, dies zu tun, ohne die Methoden explizit zu binden. Wenn Sie babel verwenden, können Sie jede Methode in der React-Komponente als Pfeilfunktion deklarieren. Es gibt Beispiele hier: babeljs.io/blog/2015/06/07/react-on-es6-plus
Ivan
7
Aber warum ist das überhaupt nicht thisdefiniert? Ich weiß, dass thisJavascript davon abhängt, wie die Funktion aufgerufen wird, aber was ist hier los?
Außenseiter
1
aber wie kann ich das machen, wenn die Funktion erst nach dem Konstruktor definiert wird? Ich erhalte die
rex
86

Es gibt verschiedene Möglichkeiten.

Eine besteht darin, this.onToggleLoop = this.onToggleLoop.bind(this);den Konstruktor hinzuzufügen .

Ein weiterer Grund sind Pfeilfunktionen onToggleLoop = (event) => {...}.

Und dann ist da noch etwas onClick={this.onToggleLoop.bind(this)}.

J. Mark Stevens
quelle
1
Warum funktioniert onToogleLoop = () => {}? Ich habe das gleiche Problem und ich habe es in meinem Konstruktor gefunden, aber es hat nicht funktioniert ... und jetzt habe ich Ihren Beitrag gesehen und meine Methode durch eine Pfeilfunktionssyntax ersetzt und es funktioniert. Kannst du mir das erklären ?
Guchelkaben
5
von developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… ; Eine Pfeilfunktion erstellt dies nicht selbst, sondern dieser Wert des umschließenden Ausführungskontexts wird verwendet.
J. Mark Stevens
1
Beachten Sie, dass das Inline-Binden in onClickjedem Rendering eine neue Funktion zurückgibt und es daher so aussieht, als ob ein neuer Wert für die Requisite übergeben wurde, mit dem shouldComponentUpdatein PureComponents herumgespielt wird .
Ninjakannon
24

Schreiben Sie Ihre Funktion folgendermaßen:

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

Fettpfeilfunktionen

Die Bindung für das Schlüsselwort ist außerhalb und innerhalb der Fettpfeilfunktion gleich. Dies unterscheidet sich von Funktionen, die mit function deklariert wurden und diese beim Aufruf an ein anderes Objekt binden können. Das Verwalten dieser Bindung ist sehr praktisch für Vorgänge wie das Mapping: this.items.map (x => this.doSomethingWith (x)).

ShaTin
quelle
Wenn ich das mache, bekomme ich ReferenceError: fields are not currently supported.
Pavel Komarov
Es funktioniert, wenn ich im Konstruktor this.func = () => {...} sage, aber ich betrachte das als doof und möchte es nach Möglichkeit vermeiden.
Pavel Komarov
So schrecklich, dass Sie in React keine normale Klassensyntax verwenden können!
Kokodoko
11

Ich bin in einer Renderfunktion auf eine ähnliche Bindung gestoßen und habe den Kontext thisfolgendermaßen übergeben:

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

Ich habe auch verwendet:

{someList.map((listItem, index) =>
    <div onClick={this.someFunction.bind(this, listItem)} />
)}
duhaime
quelle
Das sind viele unnötige Funktionen, die Sie dort jedes Mal erstellen, wenn die Liste gerendert wird ...
TJ Crowder
@TJCrowder Ja, es stimmt, diese Funktionen werden bei jedem Aufruf des Renderings neu erstellt. Es ist besser, die Funktionen als Klassenmethoden zu erstellen und sie einmal an die Klasse zu binden, aber für Anfänger kann die manuelle Kontextbindung hilfreich sein
duhaime
2

Sie sollten beachten, dass dies thisdavon abhängt, wie die Funktion aufgerufen wird, dh: Wenn eine Funktion als Methode eines Objekts aufgerufen wird, thiswird sie auf das Objekt gesetzt, für das die Methode aufgerufen wird.

thisist im JSX-Kontext als Ihr Komponentenobjekt verfügbar, sodass Sie Ihre gewünschte Methode inline als thisMethode aufrufen können .

Wenn Sie nur einen Verweis auf Funktion / Methode übergeben, wird React diese anscheinend als unabhängige Funktion aufrufen.

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
Jakub Kutrzeba
quelle
1
Richtig, Sie können sogar die erste Zeile verwenden, onClick={this.onToggleLoop}vorausgesetzt, Sie haben in Ihrer Komponentenklasse ein Feld (eine Eigenschaft) definiertonToggleLoop = () => /*body using 'this'*/
gvlax
1

Wenn Sie babel verwenden, binden Sie 'this' mit dem ES7-Bindungsoperator 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>
    )
  }

}
Henry Jacob
quelle
0

Wenn Sie Ihre erstellte Methode in den Lebenszyklusmethoden wie componentDidMount ... aufrufen , können Sie nur die Funktion this.onToggleLoop = this.onToogleLoop.bind(this)und den Fettpfeil verwenden onToggleLoop = (event) => {...}.

Der normale Ansatz der Deklaration einer Funktion im Konstruktor funktioniert nicht, da die Lebenszyklusmethoden früher aufgerufen werden.

Guchelkaben
quelle
0

in meinem Fall war dies die Lösung = () => {}

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

In meinem Fall musste ich für eine zustandslose Komponente, die den Ref mit forwardRef erhalten hat, das tun, was hier gesagt wird: https://itnext.io/reusing-the-ref-from-forwardref-with-react-hooks-4ce9df693dd

Von hier aus (onClick hat keinen Zugriff auf das Äquivalent von 'this')

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

Dazu (es funktioniert)

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} } />
})
GWorking
quelle