Não é possível acessar a instância React (this) dentro do manipulador de events

Estou escrevendo um componente simples no ES6 (com BabelJS) e funções this.setState não está funcionando.

Erros típicos incluem algo como

Não é possível ler a propriedade ‘setState’ de undefined

ou

this.setState não é uma function

Você sabe por quê? Aqui está o código:

 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 ( 

The input form is here:

Title:
) } } export default SomeClass

this.changeContent precisa estar ligado à instância do componente via this.changeContent.bind(this) antes de ser passado como o prop onChange , caso contrário, a variável this no corpo da function não se referirá à instância do componente, mas à window . Veja Function :: bind .

Ao usar React.createClass vez de classs ES6, todo método de ciclo de vida não definido em um componente é automaticamente ligado à instância do componente. Veja Autobinding .

Esteja ciente de que ligar uma function cria uma nova function. Você pode vinculá-lo diretamente em render, o que significa que uma nova function será criada toda vez que o componente renderizar ou vinculá-lo em seu construtor, que será acionado apenas uma vez.

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

vs

 render() { return ; } 

As referências são definidas na instância do componente e não no React.refs : é necessário alterar React.refs.someref para this.refs.someref . Você também precisará ligar o método sendContent à instância do componente para que this se refira a ele.

Morhaus está correto, mas isso pode ser resolvido sem bind .

Você pode usar uma function de seta junto com a proposta de propriedades de class :

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

Como a function de seta é declarada no escopo do construtor e porque as funções de seta mantêm this partir de seu escopo declarante, tudo funciona. A desvantagem aqui é que estas não serão funções no protótipo, elas serão todas recriadas com cada componente. No entanto, isso não é muito ruim, já que o bind resulta na mesma coisa.

Esse problema é uma das primeiras coisas que a maioria de nós experimenta, ao fazer a transição da syntax de definição de componente React.createClass() para a maneira de estender React.Component da class ES6.

Isso é causado por diferenças no contexto em React.createClass() vs extends React.Component .

O uso de React.createClass() automaticamente vinculará this contexto (valores) corretamente, mas esse não é o caso quando usar classs ES6. Ao fazê-lo da maneira ES6 (estendendo React.Component ), o contexto this é null por padrão. As propriedades da class não se ligam automaticamente à instância da class React (componente).


Abordagens para resolver este problema

Eu conheço um total de 4 abordagens gerais.

  1. Vincule suas funções no construtor de class . Considerado por muitos como uma abordagem de melhor prática que evita tocar no JSX e não cria uma nova function em cada renderização do componente.

     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 (  ); } } 
  2. Vincule suas funções inline . Você ainda pode encontrar essa abordagem usada aqui e ali em alguns tutoriais / artigos / etc, por isso é importante que você esteja ciente disso. É o mesmo conceito como # 1, mas esteja ciente de que a binding de uma function cria uma nova function para cada nova renderização.

     class SomeClass extends React.Component { handleClick() { console.log(this); // the React Component instance } render() { return (  ); } } 
  3. Use uma function de seta gorda . Até que a seta funcione, cada nova function definiu seu próprio valor. No entanto, a function de seta não cria seu próprio contexto, portanto, isso tem o significado original da instância do componente React. Portanto, podemos:

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

    ou

     class SomeClass extends React.Component { handleClick = () => { console.log(this); // the React Component instance } render() { return (  ); } } 
  4. Use a biblioteca de funções do utilitário para vincular automaticamente suas funções . Existem algumas bibliotecas de utilidades por aí, que automaticamente fazem o trabalho para você. Aqui estão alguns dos populares, só para mencionar alguns:

    • O Autobind Decorator é um pacote NPM que vincula methods de uma class à instância correta, mesmo quando os methods são desanexados. O pacote usa @autobind antes dos methods para vincular this à referência correta ao contexto do componente.

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

      Autobind Decorator é inteligente o suficiente para nos permitir ligar todos os methods dentro de uma class de componentes de uma só vez, assim como a abordagem # 1.

    • Classe Autobind é outro pacote NPM que é amplamente usado para resolver este problema de binding. Ao contrário do Autobind Decorator, ele não usa o padrão de decorador, mas realmente usa apenas uma function dentro de seu construtor que automaticamente vincula os methods do Component à referência correta 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 (  ); } } 

      PS: Outra biblioteca muito semelhante é o React Autobind .


Recomendação

Se eu fosse você, eu ficaria com a abordagem # 1. No entanto, assim que você obtiver muitos binds em seu construtor de class, recomendo que você explore uma das bibliotecas auxiliares mencionadas na abordagem # 4.


De outros

Não está relacionado ao problema que você tem, mas você não deve usar em demasia os refs .

Sua primeira inclinação pode ser usar as referências para “fazer as coisas acontecerem” no seu aplicativo. Se esse for o caso, reserve um momento e pense mais criticamente sobre onde o estado deve pertencer à hierarquia do componente.

Para fins semelhantes, assim como o que você precisa, usar um componente controlado é o caminho preferido. Eu sugiro que você considere usar seu state componente . Assim, você pode simplesmente acessar o valor da seguinte forma: this.state.inputContent .

Precisamos ligar a function de evento com o componente no construtor da seguinte maneira,

 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 ( 

The input form is here:

Title:
) } } export default SomeClass

obrigado

Esse problema está acontecendo porque this.changeContent e onClick={this.sendContent} não estão vinculados a isso da instância do componente.

Há outra solução (além de usar bind () no construtor ()) para usar as funções de seta do ES6 que compartilham o mesmo escopo léxico do código circundante e manter isso , assim você pode mudar seu código em render () para estar :

 render() { return (  this.changeContent() } />  ) } 

Olá, se você não quiser se preocupar com a binding da sua function. Você pode usar ‘class-autobind’ e importá-lo assim

 import autobind from 'class-autobind'; class test extends Component { constructor(props){ super(props); autobind(this); } 

Não escreva autobind antes da chamada super porque não vai funcionar

Caso você queira manter a binding na syntax do construtor, você pode usar o operador bind da proposta e transformar seu código como segue:

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

Ao invés de :

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

muito mais simples, não há necessidade de bind(this) ou fatArrow .

Você pode resolver isso de três maneiras

1.Bind a function de evento no próprio construtor da seguinte forma

 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 ( 

The input form is here:

Title:
) } } export default SomeClass

2. Vincular quando é chamado

 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 ( 

The input form is here:

Title:
) } } export default SomeClass

3. usando as funções de seta

 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 ( 

The input form is here:

Title:
) } } export default SomeClass

Minha recomendação é usar as funções de seta como propriedades

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

e não use funções de seta como

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

porque segunda abordagem irá gerar nova function toda chamada de renderização de fato isso significa novo ponteiro nova versão de adereços, do que se você posteriormente se preocupa com performance você pode usar React.PureComponent ou em React.Component você pode sobrescrever shouldComponentUpdate (nextProps, nextState) e verificação superficial quando adereços chegaram

esse problema acontece depois de react15.0, qual manipulador de events não ligou automaticamente ao componente. Portanto, você deve vincular isso ao componente manualmente sempre que o manipulador de events for chamado.


Existem vários methods para resolver o problema. mas você precisa saber qual método é melhor e por quê? Em geral, recomendamos que vincule suas funções no construtor de class ou use uma function de seta.

 // method 1: use a arrow function class ComponentA extends React.Component { eventHandler = () => { console.log(this) } render() { return (  ); } // 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 (  ); } 

Esses dois methods não criarão uma nova function quando o componente renderizar toda vez. Portanto, nosso ChildComponent não se renderá por causa da nova function props mudar, ou pode produzir o problema de desempenho.

Você pode resolver isso seguindo estas etapas

Altere a function sendContent com

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

Alterar function de renderização com

 this.changeContent(event)} />  

Bem, eu encontrei usando funções de seta mais promissoras. Como isso lhe dará consistência e você pode passar vários parâmetros facilmente.

  

Passe mais parâmetros como você deseja. Mais claro que ligar.

Embora as respostas anteriores tenham fornecido a visão geral básica das soluções (isto é, encadernação, funções de flechas, decoradores que fazem isso para você), ainda não encontrei uma resposta que realmente explica por que isso é necessário – que na minha opinião é a raiz de confusão, e leva a passos desnecessários, tais como rebinding desnecessário e seguindo cegamente o que os outros fazem.

this é dynamic

Para entender esta situação específica, uma breve introdução sobre como this funciona. A principal coisa aqui é que this é uma binding de tempo de execução e depende do contexto de execução atual. Por isso, é comumente chamado de “contexto” – informações sobre o contexto atual de execução, e por que você precisa vincular é porque perde “contexto”. Mas deixe-me ilustrar o problema com um trecho:

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

Neste exemplo, obtemos 3 , conforme esperado. Mas pegue este exemplo:

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

Pode ser inesperado descobrir que ele é indefinido – para onde foram os 3 ? A resposta está no “contexto” ou em como você executa uma function. Compare como chamamos as funções:

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

Observe a diferença. No primeiro exemplo, estamos especificando exatamente onde o método da bar 1 está localizado – no object foobar :

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

Mas, no segundo, armazenamos o método em uma nova variável e usamos essa variável para chamar o método, sem explicitamente declarar onde o método realmente existe, perdendo assim o contexto :

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

E aí está o problema, quando você armazena um método em uma variável, as informações originais sobre onde esse método está localizado (o contexto no qual o método está sendo executado) são perdidas. Sem essa informação, em tempo de execução, não há como o intérprete JavaScript vincular o correto – sem contexto específico, this não funciona como esperado 2 .

Relacionando-se com Reagir

Aqui está um exemplo de um componente React (abreviado por brevidade) sofrendo this problema:

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

Mas por que e como a seção anterior se relaciona com isso? Isso é porque eles sofrem de uma abstração do mesmo problema. Se você observar como o React manipula os manipuladores de events :

 // 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, ie "onClick" let listener = props[registrationName]; // Later, listener is called 

Então, quando você faz onClick={this.handleClick} , o método this.handleClick é eventualmente atribuído à variável listener 3 . Mas agora você vê o problema surgir – já que this.handleClick ao listener , não especificamos mais exatamente de onde o handleClick está vindo! Do ponto de vista do React, o listener é apenas alguma function, não anexado a nenhum object (ou, neste caso, à instância do componente React). Perdemos o contexto e, portanto, o interpretador não pode inferir um valor para usar dentro do handleClick .

Por que a encadernação funciona

Você pode estar se perguntando, se o interpretador decidir o valor em tempo de execução, por que posso vincular o manipulador para que ele funcione ? Isso ocorre porque você pode usar a Function#bind para garantir this valor em tempo de execução. Isso é feito definindo uma propriedade de binding interna em uma function, permitindo que ela não inferir this :

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

Quando essa linha é executada, presumivelmente no construtor, a corrente é capturada (a instância do componente React) e definida como uma binding interna de uma function inteiramente nova, retornada da Function#bind . Isso garante que, quando this estiver sendo calculado em tempo de execução, o intérprete não tentará inferir nada, mas use o valor fornecido fornecido this você.

Por que as propriedades das funções de seta funcionam

As propriedades da class de function de seta funcionam atualmente através do Babel com base na transpilação:

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

Torna-se:

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

E isso funciona devido ao fato de que as funções de seta não ligam isso a si mesmas, mas tomam this de seu escopo de inclusão. Nesse caso, o constructor é this , que aponta para a instância do componente React – dando a você o correto. 4


1 Eu uso “método” para se referir a uma function que deveria estar ligada a um object, e “function” para aqueles que não estão.

2 No segundo trecho, undefined é registrado em vez de 3, porque this padronizado para o contexto de execução global ( window quando não está no modo estrito, ou então undefined ) quando não pode ser determinado via contexto específico. E no exemplo window.foo não existe, resultando em indefinido.

3 Se você for ao buraco de como os events na fila de events são executados, invokeGuardedCallback é chamado no ouvinte.

4 Na verdade, é muito mais complicado . Reagir internamente tenta usar a Function#apply nos ouvintes para seu próprio uso, mas isso não funciona funções de seta como eles simplesmente não vinculam this . Isso significa que, quando this é avaliado na function de seta, this é resolvido em cada ambiente léxico de cada contexto de execução do código atual do módulo. O contexto de execução que finalmente resolve ter this binding é o construtor, que tem um apontando para a instância atual do componente React, permitindo que ele funcione.

Você está usando o ES6 para que as funções não se vinculem ao contexto “this” automaticamente. Você tem que ligar manualmente a function ao contexto.

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

Suas funções precisam de binding para jogar com estado ou adereços em manipuladores de events

No ES5, vincule as funções do manipulador de events apenas no construtor, mas não vincule diretamente na renderização. Se você vincular diretamente na renderização, ela criará uma nova function toda vez que seu componente renderizar e renderizar novamente. Então você deve sempre ligá-lo no construtor

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

No ES6, use as funções de seta

Quando você usa as funções de seta, não precisa vincular e também pode evitar problemas relacionados ao escopo

 sendContent = (event) => { }