Reaguj: „this” jest niezdefiniowane w funkcji składnika

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

Chcę zaktualizować loopActivestan na przełączniku, ale thisobiekt jest niezdefiniowany w programie obsługi. Zgodnie z dokumentacją w tutorialu thispowinienem odnieść się do komponentu. Czy coś mi brakuje?

Maximus S.
źródło

Odpowiedzi:

211

ES6 React.Component nie wiąże automatycznie metod ze sobą. Musisz je samodzielnie związać w konstruktorze. Lubię to:

constructor (props){
  super(props);

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

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

}
Ivan
źródło
23
Jeśli zmienisz właściwość onClick na () => this.onToggleLooppo przeniesieniu funkcji onToggleLoop do klasy reagowania, również zadziała.
Sam
71
Czy naprawdę musisz wiązać każdą metodę każdej klasy reakcji? Czy to nie jest trochę szalone?
Alex L
6
@AlexL Istnieją sposoby na zrobienie tego bez jawnego wiązania metod. jeśli używasz babel, możesz zadeklarować każdą metodę w komponencie React jako funkcje strzałkowe. Oto przykłady: babeljs.io/blog/2015/06/07/react-on-es6-plus
Ivan
7
Ale dlaczego jest thisnieokreślony w pierwszej kolejności? Wiem, że thisw JavaScript zależy od tego, jak wywoływana jest funkcja, ale co się tutaj dzieje?
Maverick
1
ale jak mogę to zrobić, jeśli funkcja nie jest zdefiniowana przed konstruktorem? Jeśli spróbuję to zrobić w konstruktorze, otrzymuję komunikat „Cannot read property 'bind' of undefined" (Nie można odczytać właściwości 'bind' undefined), nawet jeśli moja funkcja jest zdefiniowana w klasie :(
rex
86

Jest kilka sposobów.

Jednym z nich jest dodanie this.onToggleLoop = this.onToggleLoop.bind(this);konstruktora.

Innym są funkcje strzałkowe onToggleLoop = (event) => {...}.

A potem jest onClick={this.onToggleLoop.bind(this)}.

J. Mark Stevens
źródło
1
Dlaczego onToogleLoop = () => {} działa? Mam ten sam problem i powiązałem go w moim konstruktorze, ale to nie zadziałało ... a teraz widziałem twój post i zastąpiłem moją metodę składnią funkcji strzałki i działa. Czy możesz mi to wyjaśnić?
Guchelkaben
5
z developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… ; Funkcja strzałkowa nie tworzy własnego this, używana jest wartość this otaczającego kontekstu wykonania.
J. Mark Stevens,
1
Należy pamiętać, że wiązanie inline w onClickpowróci nowa funkcja każdy czyni, a więc wygląda na to nowa wartość została przekazana do podpory, bawiących się shouldComponentUpdatew PureComponents.
Ninjakannon
24

Napisz swoją funkcję w ten sposób:

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

Funkcje Fat Arrow

powiązanie dla słowa kluczowego to jest to samo na zewnątrz i wewnątrz funkcji grubej strzałki. Różni się to od funkcji zadeklarowanych za pomocą funkcji, które mogą powiązać to z innym obiektem po wywołaniu. Utrzymanie tego powiązania jest bardzo wygodne w przypadku operacji takich jak mapowanie: this.items.map (x => this.doSomethingWith (x)).

ShaTin
źródło
Jeśli to zrobię, dostanę ReferenceError: fields are not currently supported.
Pavel Komarov
Działa, jeśli w konstruktorze powiem this.func = () => {...}, ale uważam to za trochę głupie i chcę tego uniknąć, jeśli to możliwe.
Pavel Komarov
Tak straszne, że nie możesz używać normalnej składni klas w Reakcie!
Kokodoko,
11

Natknąłem się na podobny bind w funkcji renderującej i skończyłem przekazując kontekst thisw następujący sposób:

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

Użyłem też:

{someList.map((listItem, index) =>
    <div onClick={this.someFunction.bind(this, listItem)} />
)}
duhaime
źródło
To dużo niepotrzebnych funkcji, które tam tworzysz, za każdym razem, gdy lista jest renderowana ...
TJ Crowder,
@TJCrowder Tak, to prawda, te funkcje są tworzone od nowa przy każdym wywołaniu renderowania. Lepiej jest utworzyć funkcje jako metody klasowe i raz powiązać je z klasą, ale dla początkujących pomocne może być ręczne wiązanie kontekstu
duhaime,
2

Należy zauważyć, że thiszależy to od tego, jak wywoływana jest funkcja, tj .: kiedy funkcja jest wywoływana jako metoda obiektu, thisjest ona przypisywana do obiektu, na którym jest wywoływana metoda.

thisjest dostępny w kontekście JSX jako obiekt komponentu, więc możesz wywołać żądaną metodę inline jako thismetodę.

Jeśli po prostu przekażesz referencję do funkcji / metody, wydaje się, że reakcja wywoła ją jako niezależną funkcję.

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
źródło
1
Racja, możesz nawet użyć pierwszej linii, tj. onClick={this.onToggleLoop}onToggleLoop = () => /*body using 'this'*/
Pod
1

Jeśli używasz babel, łączysz „this” za pomocą operatora wiązania 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>
    )
  }

}
Henry Jacob
źródło
0

Jeśli wywołasz utworzoną metodę w metodach cyklu życia, takich jak componentDidMount ..., możesz użyć tylko funkcji this.onToggleLoop = this.onToogleLoop.bind(this)i grubej strzałki onToggleLoop = (event) => {...}.

Normalne podejście do deklaracji funkcji w konstruktorze nie zadziała, ponieważ metody cyklu życia są wywoływane wcześniej.

Guchelkaben
źródło
0

w moim przypadku to było rozwiązanie = () => {}

methodName = (params) => {
//your code here with this.something
}
Alex
źródło
0

W moim przypadku dla składnika bezstanowego, który otrzymał ref z forwardRef, musiałem zrobić to, co jest tutaj powiedziane https://itnext.io/reusing-the-ref-from-forwardref-with-react-hooks-4ce9df693dd

Z tego (onClick nie ma dostępu do odpowiednika „this”)

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

Do tego (to działa)

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
źródło