Реагировать: «this» не определено внутри функции компонента.

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

Я хочу обновить loopActiveсостояние при переключении, но thisобъект не определен в обработчике. Согласно учебному документу, я thisдолжен ссылаться на компонент. Я что-то упускаю?

Maximus S
источник

Ответы:

215

ES6 React.Componentне привязывает методы к себе автоматически. Их нужно связать самостоятельно constructor. Как это:

constructor (props){
  super(props);
  
  this.state = {
      loopActive: false,
      shuffleActive: false,
    };
  
  this.onToggleLoop = this.onToggleLoop.bind(this);

}
Иван
источник
27
если вы измените свое свойство () => this.onToggleLooponClick на после перемещения функции onToggleLoop в свой класс реакции, она также будет работать.
Сэм
72
Вам действительно нужно связывать каждый метод каждого класса реакции? Разве это не немного безумие?
Alex L
7
@AlexL Есть способы сделать это без явной привязки методов. если вы используете babel, можно объявить каждый метод компонента React как стрелочные функции. Вот примеры: babeljs.io/blog/2015/06/07/react-on-es6-plus
Иван
7
Но почему вообще thisundefined? Я знаю, что thisв Javascript зависит от того, как вызывается функция, но что здесь происходит?
darKnight 03
1
но как я могу это сделать, если функция не определена до конструктора? Я получаю сообщение «Невозможно прочитать свойство 'bind' of undefined», если я попытаюсь сделать это в конструкторе, даже если моя функция определена в классе :(
rex
95

Есть несколько способов.

Один из них - добавить this.onToggleLoop = this.onToggleLoop.bind(this);в конструктор.

Другой - стрелочные функции onToggleLoop = (event) => {...}.

И еще есть onClick={this.onToggleLoop.bind(this)}.

Дж. Марк Стивенс
источник
1
Почему работает onToogleLoop = () => {}? У меня та же проблема, и я добавил ее в свой конструктор, но она не сработала ... и теперь я увидел ваш пост и заменил свой метод синтаксисом стрелочной функции, и он работает. Вы можете мне это объяснить?
Guchelkaben
6
с сайта developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… ; Стрелочная функция не создает свой собственный this, используется значение this окружающего контекста выполнения.
Дж. Марк Стивенс
1
Обратите внимание, что привязка, встроенная в onClickобъект, будет возвращать новую функцию при каждом рендеринге, и, таким образом, похоже, что для свойства было передано новое значение, что нарушает его shouldComponentUpdateв PureComponents.
Ninjakannon
27

Напишите свою функцию так:

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

Функции жирной стрелки

привязка ключевого слова this одинакова как внутри, так и снаружи функции жирной стрелки. Это отличается от функций, объявленных с помощью функции, которая может привязать this к другому объекту при вызове. Сохранение привязки this очень удобно для таких операций, как отображение: this.items.map (x => this.doSomethingWith (x)).

Шатин
источник
Если я это сделаю, то получу ReferenceError: fields are not currently supported.
Павел Комаров
Это сработает, если внутри конструктора я скажу this.func = () => {...}, но я считаю это глупостью и хочу по возможности избегать этого.
Павел Комаров
Настолько ужасно, что вы не можете использовать обычный синтаксис класса в React!
Kokodoko
11

Я столкнулся с похожей привязкой в ​​функции рендеринга и в итоге передал контекст thisследующим образом:

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

Я также использовал:

{someList.map((listItem, index) =>
    <div onClick={this.someFunction.bind(this, listItem)} />
)}
Duhaime
источник
Вы создаете там много ненужных функций каждый раз, когда отображается список ...
TJ Crowder
@TJCrowder Да, это правда, что эти функции создаются заново каждый раз при вызове рендера. Лучше создать функции как методы класса и один раз связать их с классом, но для новичков может быть полезна ручная привязка контекста
duhaime
3

Вы должны заметить, что это thisзависит от того, как функция вызывается, например: когда функция вызывается как метод объекта, она thisустанавливается на объект, для которого вызывается метод.

thisдоступен в контексте JSX как объект вашего компонента, поэтому вы можете вызвать нужный метод встроенным в качестве thisметода.

Если вы просто передадите ссылку на функцию / метод, кажется, что реакция вызовет ее как независимую функцию.

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
Правильно, вы даже можете использовать первую строку, т.е. onClick={this.onToggleLoop}при условии, что в вашем классе компонента вы определили поле (свойство)onToggleLoop = () => /*body using 'this'*/
gvlax
1

Если вы используете babel, вы привязываете this с помощью оператора привязки 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

Если вы вызываете созданный вами метод в методах жизненного цикла, таких как componentDidMount ..., вы можете использовать только this.onToggleLoop = this.onToogleLoop.bind(this)и функцию жирной стрелки onToggleLoop = (event) => {...}.

Обычный подход к объявлению функции в конструкторе не работает, потому что методы жизненного цикла вызываются раньше.

Гучелкабен
источник
0

в моем случае это было решение = () => {}

methodName = (params) => {
//your code here with this.something
}
Alex
источник
0

В моем случае для компонента без состояния, который получил ссылку с помощью forwardRef, мне пришлось сделать то, что здесь сказано https://itnext.io/reusing-the-ref-from-forwardref-with-react-hooks-4ce9df693dd

Отсюда (onClick не имеет доступа к эквиваленту this)

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

К этому (это работает)

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
источник
0

Вы можете переписать способ вызова метода onToggleLoop из метода render ().

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={(event) => this.onToggleLoop(event)}
      spin={this.state.loopActive}
    />       
  </div>
    );
  }

Документация React показывает этот шаблон при вызове функций из выражений в атрибутах.

Т. Вебстер
источник