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

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

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

Не удается прочитать свойство 'setState' из неопределенного

или же

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

16 ответов

Решение

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

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

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

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

против

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

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

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

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

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

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

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

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

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

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


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

Я знаю всего 4 общих подхода.

  1. Свяжите свои функции в конструкторе класса. Многие считают это лучшим подходом, который вообще не затрагивает 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>
        );
      }
    }
    
  2. Свяжите свои функции в строке. Вы все еще можете найти этот подход, использованный здесь и там в некоторых уроках / статьях / и т. Д., Поэтому важно, чтобы вы знали об этом. Это та же концепция, что и #1, но имейте в виду, что привязка функции создает новую функцию для каждого повторного рендеринга.

    class SomeClass extends React.Component {
      handleClick() {
        console.log(this); // the React Component instance
      }
      render() {
        return (
          <button onClick={this.handleClick.bind(this)}></button>
        );
      }
    }
    
  3. Используйте функцию жирной стрелки. До функций стрелок каждая новая функция определяла свою собственную 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>
        );
      }
    }
    
  4. Используйте библиотеку служебных функций для автоматического связывания ваших функций. Существует несколько утилитарных библиотек, которые автоматически сделают всю работу за вас. Вот некоторые из популярных, просто чтобы упомянуть несколько:

    • 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.

    • Class Autobind - это еще один пакет NPM, который широко используется для решения этой проблемы связывания. В отличие от Autobind Decorator, он не использует шаблон декоратора, а просто использует внутри вашего конструктора функцию, которая автоматически связывает методы Компонента с правильной ссылкой 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.


Другой

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

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

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

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

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's 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 в конце концов присваивается переменной listener3 Но теперь вы видите, что проблема возникает, так как мы назначили this.handleClick в listenerмы больше не указываем, где именно handleClick идет от! С точки зрения React, listener это просто какая-то функция, не привязанная к какому-либо объекту (или, в данном случае, экземпляру компонента React). Мы потеряли контекст, и поэтому переводчик не может сделать вывод this значение для использования внутри handleClick,

Почему переплет работает

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

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

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

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

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

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

становится:

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

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


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

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

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

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

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

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. Связывайтесь, когда это называется

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

Вы можете решить эту проблему, выполнив следующие действия

Измените функцию 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>

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

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

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

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

в конструкторе ():

for (let member of Object.getOwnPropertyNames(Object.getPrototypeOf(this))) {
    this[member] = this[member].bind(this)
}

или создайте эту функцию в файле global.jsx

export function bindAllFunctions({ bindTo: dis }) {
for (let member of Object.getOwnPropertyNames(Object.getPrototypeOf(dis))) {
    dis[member] = dis[member].bind(dis)
    }
}

и в вашем конструкторе () назовите его так:

bindAllFunctions({ bindTo: this })

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

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

Спасибо

Я рекомендую использовать функции стрелок в качестве свойств

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) и мелкая проверка, когда прибыл реквизит

bind(this) может решить эту проблему, и в настоящее время мы можем использовать еще 2 способа для достижения этой цели, если вам не нравится использовать bind,

1) Как традиционный способ, мы можем использовать bind(this) в конструкторе, так что когда мы используем функцию в качестве обратного вызова JSX, контекст this это сам класс.

class App1 extends React.Component {
  constructor(props) {
    super(props);
    // If we comment out the following line,
    // we will get run time error said `this` is undefined.
    this.changeColor = this.changeColor.bind(this);
  }

  changeColor(e) {
    e.currentTarget.style.backgroundColor = "#00FF00";
    console.log(this.props);
  }

  render() {
    return (
      <div>
        <button onClick={this.changeColor}> button</button>
      </div>
    );
  }
}

2) Если мы определяем функцию как атрибут / поле класса с помощью функции стрелки, нам не нужно использовать bind(this) больше

class App2 extends React.Component {
  changeColor = e => {
    e.currentTarget.style.backgroundColor = "#00FF00";
    console.log(this.props);
  };
  render() {
    return (
      <div>
        <button onClick={this.changeColor}> button 1</button>
      </div>
    );
  }
}

3) Если мы используем функцию стрелки в качестве обратного вызова JSX, нам не нужно использовать bind(this) или. И еще больше, мы можем передать параметры. Выглядит хорошо, не правда ли? но его недостатком является проблема производительности, подробности см. в документе ReactJS.

class App3 extends React.Component {
  changeColor(e, colorHex) {
    e.currentTarget.style.backgroundColor = colorHex;
    console.log(this.props);
  }
  render() {
    return (
      <div>
        <button onClick={e => this.changeColor(e, "#ff0000")}> button 1</button>
      </div>
    );
  }
}

И я создал Codepen для демонстрации этих фрагментов кода, надеюсь, это поможет.

Александр Кирзенберг прав, но еще одна важная вещь, на которую стоит обратить внимание, это то, куда вы кладете свой переплет Я застрял с ситуацией в течение нескольких дней (вероятно, потому что я новичок), но в отличие от других, я знал о 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"

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

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

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

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

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

sendContent = (event) => {

}

Эта проблема возникает после act15.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 не будет повторно выполнять рецензирование из-за смены реквизита новой функции или может вызвать проблемы с производительностью.

Решение:

  1. Без явного связывания, bind с именем метода вы можете использовать синтаксические функции жирной стрелки ()=>{}, которые поддерживают контекст this,
import React from 'react'

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

  sendContent = (e) => {
    console.log('sending input content ',this.state.inputContent);
  }

  changeContent = (e) => {
    this.setState({inputContent: e.target.value},()=>{
      console.log('STATE:',this.state);
    })
  } 

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

export default SomeClass

Другие решения:

  1. Свяжите свои функции в конструкторе класса.

  2. Свяжите свои функции в шаблоне JSX, избегая скобок {}{this.methodName.bind (this)}

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

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

Вместо:

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

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

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

import autobind from 'class-autobind';

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

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

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

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

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

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

render() {
    return (

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

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

    )
  }
Другие вопросы по тегам