Невозможно получить доступ к экземпляру React (this) внутри обработчика событий

157

Я пишу простой компонент в ES6 (с BabelJS), а функции this.setState не работают.

Типичные ошибки включают что-то вроде

Невозможно прочитать свойство 'setState' из undefined

или

this.setState не является функцией

Знаешь почему? Вот код:

import React from 'react'

class SomeClass extends React.Component {
  constructor(props) {
    super(props)
    this.state = {inputContent: 'startValue'}
  }

  sendContent(e) {
    console.log('sending input content '+React.findDOMNode(React.refs.someref).value)
  }

  changeContent(e) {
    this.setState({inputContent: e.target.value})
  } 

  render() {
    return (
      <div>
        <h4>The input form is here:</h4>
        Title: 
        <input type="text" ref="someref" value={this.inputContent} 
          onChange={this.changeContent} /> 
        <button onClick={this.sendContent}>Submit</button>
      </div>
    )
  }
}

export default SomeClass
  • 0
    Это не проблема, но вы должны избегать ссылок .
  • 0
    @FakeRainBrigand как бы вы решили это без ссылок здесь?
Показать ещё 4 комментария
Теги:
ecmascript-6
babeljs

16 ответов

206
Лучший ответ

this.changeContent должен быть привязан к экземпляру компонента через this.changeContent.bind(this) перед передачей в качестве onChange prop, в противном случае переменная this в теле функции не будет ссылаться на экземпляр компонента, а на window. См. Функция:: привязка.

При использовании React.createClass вместо классов ES6 каждый метод, не связанный с жизненным циклом, определенный на компоненте, автоматически привязывается к экземпляру компонента. См. Autobinding.

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

constructor() {
  this.changeContent = this.changeContent.bind(this);
}

против

render() {
  return <input onChange={this.changeContent.bind(this)} />;
}

Refs заданы на экземпляре компонента, а не на React.refs: вам нужно изменить React.refs.someref на this.refs.someref. Вам также необходимо привязать метод sendContent к экземпляру компонента, чтобы this ссылался на него.

  • 5
    Хорошая вещь в привязке функции в самом конструкторе, чтобы предотвратить создание функций несколько раз
  • 0
    извините, но я не понимаю, почему this.changeContent необходимо this.changeContent к экземпляру компонента через this.changeContent.bind(this) . Я имею в виду, что мы пишем компонент через подкласс или React.Component, а в ES6 каждый метод, определенный в классе, автоматически привязывается к экземпляру, созданному через сам подкласс / класс. Почему здесь нам нужно делать это «вручную»? Это что-то особенное в отношении React? Или я беспокоюсь о динамике методов класса ES6?
Показать ещё 5 комментариев
86

Морхаус прав, но это можно решить без bind.

Вы можете использовать функцию arrow вместе с предложением свойств класса:

class SomeClass extends React.Component {
  changeContent = (e) => {
    this.setState({inputContent: e.target.value})
  } 

  render() {
    return <input type="text" onChange={this.changeContent} />;
  }
}

Поскольку функция стрелки объявлена ​​в области конструктора, и поскольку функции стрелок поддерживают this из области объявления, все это работает. Недостатком здесь является то, что они не будут функциями на прототипе, все они будут воссозданы с каждым компонентом. Тем не менее, это не так много, так как bind приводит к тому же.

  • 1
    Это прекрасно работает и в TypeScript (обычно не нужно беспокоиться о связывании в TypeScript, но я думаю, что это не так)
  • 0
    Это не работает Я получаю «объявления свойств можно использовать только в файле .ts»
Показать ещё 4 комментария
44

Эта проблема является одной из первых вещей, которые испытывает большинство из нас при переходе от синтаксиса определения компонентов React.createClass() к классу ES6 класса расширения React.Component.

Это вызвано различиями контекста this в React.createClass() vs extends React.Component.

Использование React.createClass() будет автоматически связывать контекст (значения) this правильно, но это не так, если вы используете классы ES6. При использовании ES6 (расширение React.Component) контекст this по умолчанию равен null. Свойства этого класса автоматически не привязываются к экземпляру класса React (компонент).


Подходы к решению этой проблемы

Я знаю в общей сложности 4 общих подхода.

  • Связать свои функции в конструкторе класса. Многие считают, что это лучший подход, который позволяет вообще не прикасаться к JSX и не создает новую функцию для каждого повторного рендеринга компонентов.

    class SomeClass extends React.Component {
      constructor(props) {
        super(props);
        this.handleClick = this.handleClick.bind(this);
      }
      handleClick() {
        console.log(this); // the React Component instance
      }
      render() {
        return (
          <button onClick={this.handleClick}></button>
        );
      }
    }
    
  • Свяжите свои встроенные функции. Вы все еще можете найти этот подход, используемый здесь и там в некоторых учебниках/статьях/и т.д., Поэтому важно, чтобы вы знали об этом. Это та же концепция, что и # 1, но имейте в виду, что привязка функции создает новую функцию для каждого повторного рендеринга.

    class SomeClass extends React.Component {
      handleClick() {
        console.log(this); // the React Component instance
      }
      render() {
        return (
          <button onClick={this.handleClick.bind(this)}></button>
        );
      }
    }
    
  • Используйте функцию толстой стрелки. До тех пор, пока стрелки не будут функционировать, каждая новая функция определяет свое собственное значение this. Однако функция стрелок не создает свой собственный this контекст, поэтому this имеет исходное значение из экземпляра компонента React. Поэтому мы можем:

    class SomeClass extends React.Component {
      handleClick() {
        console.log(this); // the React Component instance
      }
      render() {
        return (
          <button onClick={ () => this.handleClick() }></button>
        );
      }
    }
    

    или

    class SomeClass extends React.Component {
      handleClick = () => {
        console.log(this); // the React Component instance
      }
      render() {
        return (
          <button onClick={this.handleClick}></button>
        );
      }
    }
    
  • Использовать библиотеку функций функций для автоматической привязки ваших функций. Есть несколько библиотек утилиты, которые автоматически выполняют эту работу для вас. Вот некоторые из популярных, просто упомянуть несколько:

    • Autobind Decorator - это пакет NPM, который связывает методы класса с правильным экземпляром this, даже если методы отделяются. Пакет использует @autobind до того, как методы привяжут this к правильной ссылке к контексту компонента.

      import autobind from 'autobind-decorator';
      
      class SomeClass extends React.Component {
        @autobind
        handleClick() {
          console.log(this); // the React Component instance
        }
        render() {
          return (
            <button onClick={this.handleClick}></button>
          );
        }
      }
      

      Autobind Decorator достаточно умен, чтобы мы могли сразу связывать все методы внутри класса компонентов, как и подход № 1.

    • Класс Autobind - это еще один пакет NPM, который широко используется для решения этой проблемы привязки. В отличие от Autobind Decorator, он не использует шаблон декоратора, но на самом деле просто использует функцию внутри вашего конструктора, которая автоматически связывает методы Component с правильной ссылкой this.

      import autobind from 'class-autobind';
      
      class SomeClass extends React.Component {
        constructor() {
          autobind(this);
          // or if you want to bind only only select functions:
          // autobind(this, 'handleClick');
        }
        handleClick() {
          console.log(this); // the React Component instance
        }
        render() {
          return (
            <button onClick={this.handleClick}></button>
          );
        }
      }
      

      PS: Другая очень похожая библиотека React Autobind.


Рекомендация

Если бы я был вами, я бы придерживался подхода №1. Однако, как только вы получите тонну привязок в своем конструкторе классов, я бы рекомендовал вам изучить одну из вспомогательных библиотек, упомянутых в подходе # 4.


Другие

Это не связано с вашей проблемой, но вы не должны злоупотреблять ссылками.

В первую очередь вы можете использовать ссылки для "сделать вещи" в своем приложении. Если это так, найдите момент и более критически подумайте о том, где должно принадлежать государство в иерархии компонентов.

Для подобных целей, как и тот, который вам нужен, использование Component state. Таким образом, вы можете просто получить доступ к значению, подобному этому: this.state.inputContent.

  • 4
    Это гораздо более полно и полезно, чем принятый ответ.
  • 0
    В этом другом ответе отсутствует метод stackoverflow.com/a/34050078/788260
Показать ещё 2 комментария
1

Нам нужно связать функцию события с компонентом в конструкторе следующим образом:

import React from 'react'

class SomeClass extends React.Component {
  constructor(props) {
    super(props)
    this.state = {inputContent: 'startValue'}
    this.changeContent = this.changeContent.bind(this);
  }

  sendContent(e) {
    console.log('sending input content '+React.findDOMNode(React.refs.someref).value)
  }

  changeContent(e) {
    this.setState({inputContent: e.target.value})
  } 

  render() {
    return (
      <div>
        <h4>The input form is here:</h4>
        Title: 
        <input type="text" ref="someref" value={this.inputContent} 
          onChange={this.changeContent} /> 
        <button onClick={this.sendContent}>Submit</button>
      </div>
    )
  }
}

export default SomeClass

Спасибо

0

Александр Кирзенберг прав, но еще одна важная вещь, на которую нужно обратить внимание, - это то, где вы ставите свою привязку. Я застрял в ситуации в течение нескольких дней (возможно, потому, что я новичок), но в отличие от других, я знал о bind (который я уже применял), поэтому я просто не мог понять, почему у меня все еще есть эти ошибки. Оказывается, у меня была привязка в неправильном порядке.

Другим также является тот факт, что я вызывал функцию в "this.state", которая не знала о связывании, потому что она оказалась выше строки привязки,

Ниже было то, что у меня было (кстати, это моя первая публикация, но я думал, что это очень важно, поскольку я не мог найти решение где-нибудь еще):

constructor(props){
    super(props);

       productArray=//some array

    this.state={ 
        // Create an Array  which will hold components to be displayed
        proListing:productArray.map(product=>{return(<ProRow dele={this.this.popRow()} prodName={product.name} prodPrice={product.price}/>)})
    }

    this.popRow=this.popRow.bind(this);//This was the Issue, This line //should be kept above "this.state"
0

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

В ES5 привяжите функции обработчика событий только к конструктору, но не привязывайте непосредственно к рендерингу. Если вы выполняете привязку непосредственно в рендере, он создает новую функцию каждый раз, когда ваш компонент визуализирует и повторно отображает. Поэтому вы всегда должны связывать его в конструкторе

this.sendContent = this.sendContent.bind(this)

В ES6 используйте функции стрелок

Когда вы используете функции стрелок, вам не нужно делать привязки, и вы также можете избегать проблем, связанных с областью

sendContent = (event) => {

}
0

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

constructor(props) {
  super(props);
  this.changeContent = this.changeContent.bind(this);
}
0

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

this динамично

Чтобы понять эту конкретную ситуацию, кратко расскажите, как this работает. Главное здесь - this привязка времени выполнения и зависит от текущего контекста выполнения. Следовательно, почему он обычно упоминается как "контекст" -общая информация о текущем контексте выполнения и почему вам нужно связывать, потому что вы теряете "контекст". Но позвольте мне проиллюстрировать проблему с помощью фрагмента:

const foobar = {
  bar: function () {
    return this.foo;
  },
  foo: 3,
};
console.log(foobar.bar()); // 3, all is good!

В этом примере мы получаем 3, как и ожидалось. Но возьмите этот пример:

const barFunc = foobar.bar;
console.log(barFunc()); // Uh oh, undefined!

Может быть неожиданно обнаружить, что он регистрируется неопределенно, - где же 3? Ответ лежит в "контексте", или как вы выполняете функцию. Сравните, как мы называем функции:

// Example 1
foobar.bar();
// Example 2
const barFunc = foobar.bar;
barFunc();

Обратите внимание на разницу. В первом примере мы точно определяем, где находится bar метод 1 - на объекте foobar:

foobar.bar();
^^^^^^

Но во втором мы храним метод в новой переменной и используем эту переменную для вызова метода без явного указания того, где этот метод существует, тем самым теряя контекст:

barFunc(); // Which object is this function coming from?

И в этом проблема, когда вы храните метод в переменной, теряется исходная информация о том, где находится этот метод (контекст, в котором выполняется этот метод). Без этой информации во время выполнения невозможно, чтобы интерпретатор JavaScript не смог связать правильный this без определенного контекста, this не работает должным образом 2.

Относительно реакции

Здесь приведен пример компонента React (сокращенный для краткости), страдающий от this проблемы:

handleClick() {
  this.setState(({ clicks }) => ({ // setState is async, use callback to access previous state
    clicks: clicks + 1, // increase by 1
  }));
}

render() {
  return (
    <button onClick={this.handleClick}>{this.state.clicks}</button>
  );
}

Но почему и как относится к предыдущему разделу? Это происходит потому, что они страдают от абстракции одной и той же проблемы. Если вы посмотрите, как React обрабатывает обработчики событий:

// Edited to fit answer, React performs other checks internally
// props is the current React component props, registrationName is the name of the event handle prop, i.e "onClick"
let listener = props[registrationName];
// Later, listener is called

Итак, когда вы делаете onClick={this.handleClick}, метод this.handleClick в конечном итоге назначается на listener переменных 3. Но теперь вы видите, что проблема возникает, поскольку мы назначили this.handleClick для listener, мы больше не указываем, где именно происходит handleClick ! С точки зрения listener - это просто некоторая функция, не привязанная к какому-либо объекту (или в данном случае экземпляру компонента React). Мы потеряли контекст, и поэтому интерпретатор не может вывести this значение для использования внутри handleClick.

Почему обязательные работы

Возможно, вам интересно, если интерпретатор решает this значение во время выполнения, почему я могу привязать обработчик так, чтобы он работал? Это связано с тем, что вы можете использовать Function#bind чтобы гарантировать this значение во время выполнения. Это делается путем установки внутреннего this привязки свойства на функции, что позволяет ему не выводить this:

this.handleClick = this.handleClick.bind(this);

Когда эта линия выполнена, предположительно, в конструкторе, ток this захватывается (РЕАКТ экземпляра компоненты) и установить в качестве внутреннего this связывания совершенно новой функции, возвращается из Function#bind. Это гарантирует, что при this вычисляется во время выполнения, интерпретатор не будет пытаться вывести что - либо, но использовать при условии, this значение, которое вы придан ему.

Почему свойства функции стрелки работают

Свойства класса функции Arrow в настоящее время работают через Babel на основе транспиляции:

handleClick = () => { /* Can use this just fine here */ }

становится:

constructor() {
  super();
  this.handleClick = () => {}
}

И это работает из-за того, что функции стрелки не связывают их с этим, но берут this из их охватывающей области. В этом случае constructor this, что указывает на React экземпляр, таким образом компонент дает вам правильно this. 4


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

2 Во втором фрагменте, не определено регистрируются вместо 3, потому что this по умолчанию глобального контексту исполнения (window, когда он не в строгом режиме, или еще undefined), когда она не может быть определена с помощью конкретного контекста. А в примере window.foo не существует, что дает неопределенный.

3 Если вы спуститесь в кроличью нору того, как выполняются события в очереди событий, invokeGuardedCallback.

4 Это на самом деле намного сложнее. React внутренне пытается использовать Function#apply на слушателях для собственного использования, но это не работает с функциями стрелок, поскольку они просто не связывают this. Это означает, что когда this внутри функции стрелки фактически оценивается, this разрешается каждой лексической средой каждого контекста выполнения текущего кода модуля. Контекст выполнения, который окончательно разрешает иметь this связывание, является конструктором, который имеет this указание на текущий экземпляр компонента React, что позволяет ему работать.

0

Мы должны bind нашу функцию с this чтобы получить экземпляр функции в классе. Как и в примере

<button onClick={this.sendContent.bind(this)}>Submit</button>

Таким образом this.state будет действительным объектом.

0

Вы можете решить эту проблему следующим образом

Измените функцию sendContent на

 sendContent(e) {
    console.log('sending input content '+this.refs.someref.value)
  }

Изменить функцию рендеринга с помощью

<input type="text" ref="someref" value={this.state.inputContent} 
          onChange={(event)=>this.changeContent(event)} /> 
   <button onClick={(event)=>this.sendContent(event)}>Submit</button>
0

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


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

// method 1: use a arrow function
    class ComponentA extends React.Component {
      eventHandler = () => {
        console.log(this)
      }
      render() {
        return ( 
        <ChildComponent onClick={this.eventHandler} /> 
        );
      }

// method 2: Bind your functions in the class constructor.
    class ComponentA extends React.Component {
      constructor(props) {
        super(props);
        this.eventHandler = this.eventHandler.bind(this);
      }
      render() {
        return ( 
        <ChildComponent onClick={this.eventHandler} /> 
        );
      }

эти два метода не будут создавать новую функцию при визуализации компонента каждый раз. поэтому наш ChildComponent не будет переписываться из-за изменения новых функций реквизита или может вызвать проблемы с производительностью.

0

Моя рекомендация - использование функций стрелок как свойств

class SomeClass extends React.Component {
  handleClick = () => {
    console.log(this); // the React Component instance
  }
  render() {
    return (
      <button onClick={this.handleClick}></button>
    );
  }
}

и не используйте функции стрелок как

class SomeClass extends React.Component {
      handleClick(){
        console.log(this); // the React Component instance
      }
      render() {
        return (
          <button onClick={()=>{this.handleClick}}></button>
        );
      }
    }

потому что второй подход будет генерировать новую функцию для каждого вызова рендеринга, на самом деле это означает новую указатель новой версии реквизита, чем если бы вы позже позаботились о производительности, вы можете использовать React.PureComponent или в React.Component вы можете переопределить shouldComponentUpdate (nextProps, nextState) и неглубокой проверки при прибытии реквизитов

0

Вы можете решить это тремя способами.

1. Заблокировать функцию события в самом конструкторе следующим образом:

import React from 'react'

class SomeClass extends React.Component {
  constructor(props) {
    super(props)
    this.state = {inputContent: 'startValue'}
    this.changeContent = this.changeContent.bind(this);
  }

  sendContent(e) {
    console.log('sending input content '+React.findDOMNode(React.refs.someref).value)
  }

  changeContent(e) {
    this.setState({inputContent: e.target.value})
  } 

  render() {
    return (
      <div>
        <h4>The input form is here:</h4>
        Title: 
        <input type="text" ref="someref" value={this.inputContent} 
          onChange={this.changeContent} /> 
        <button onClick={this.sendContent}>Submit</button>
      </div>
    )
  }
}

export default SomeClass

2.Bind, когда он называется

import React from 'react'

class SomeClass extends React.Component {
  constructor(props) {
    super(props)
    this.state = {inputContent: 'startValue'}
  }

  sendContent(e) {
    console.log('sending input content '+React.findDOMNode(React.refs.someref).value)
  }

  changeContent(e) {
    this.setState({inputContent: e.target.value})
  } 

  render() {
    return (
      <div>
        <h4>The input form is here:</h4>
        Title: 
        <input type="text" ref="someref" value={this.inputContent} 
          onChange={this.changeContent} /> 
        <button onClick={this.sendContent.bind(this)}>Submit</button>
      </div>
    )
  }
}

export default SomeClass

3. Используя функции стрелок

import React from 'react'

class SomeClass extends React.Component {
  constructor(props) {
    super(props)
    this.state = {inputContent: 'startValue'}
  }

  sendContent(e) {
    console.log('sending input content '+React.findDOMNode(React.refs.someref).value)
  }

  changeContent(e) {
    this.setState({inputContent: e.target.value})
  } 

  render() {
    return (
      <div>
        <h4>The input form is here:</h4>
        Title: 
        <input type="text" ref="someref" value={this.inputContent} 
          onChange={this.changeContent} /> 
        <button onClick={()=>this.sendContent()}>Submit</button>
      </div>
    )
  }
}

export default SomeClass
0

Если вы хотите сохранить привязку в синтаксисе конструктора, вы можете использовать proposal-bind-operator и преобразовать свой код следующим образом:

constructor() {
  this.changeContent = ::this.changeContent;
}

Вместо:

constructor() {
  this.changeContent = this.changeContent.bind(this);
}

гораздо проще, не нужно bind(this) или fatArrow.

0

Здравствуйте, если вы хотите не заботиться о привязке себя к вызову функции. Вы можете использовать "class-autobind" и импортировать его таким образом

import autobind from 'class-autobind';

class test extends Component {
  constructor(props){
  super(props);
  autobind(this);
}

Не записывайте autobind перед супервызовом, потому что он не будет работать

0

Эта проблема возникает, потому что this.changeContent и onClick={this.sendContent} не привязаны к этому экземпляра компонента.

Существует другое решение (в дополнение к use bind() в конструкторе()) для использования функций стрелок ES6, которые используют одну и ту же лексическую область окружающего кода и поддерживают этот, поэтому вы можете изменить свой код в render() следующим образом:

render() {
    return (

        <input type="text"
          onChange={ () => this.changeContent() } /> 

        <button onClick={ () => this.sendContent() }>Submit</button>

    )
  }
  • 3
    Это также считается плохой практикой, так как вы создаете новую функцию каждый раз, когда компонент выполняет рендеринг. Вы можете определить свои функции в своих классах ES6 с помощью const changeContent = () => {...} чтобы автоматически связать его с самим классом.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню