无法在事件处理程序中访问React实例(this)

时间:2015-04-11 12:41:39

标签: javascript reactjs ecmascript-6 babeljs

我在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

19 个答案:

答案 0 :(得分:222)

this.changeContent需要在作为this.changeContent.bind(this)道具传递之前通过onChange绑定到组件实例,否则函数体中的this变量将不会引用组件实例,但是window。请参阅Function::bind

使用React.createClass而不是ES6类时,组件上定义的每个非生命周期方法都会自动绑定到组件实例。请参阅Autobinding

请注意,绑定函数会创建一个新函数。您可以直接在render中绑定它,这意味着每次组件渲染时都会创建一个新函数,或者在构造函数中绑定它,这只会触发一次。

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

VS

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

Refs在组件实例上设置,而不在React.refs上:您需要将React.refs.someref更改为this.refs.someref。您还需要将sendContent方法绑定到组件实例,以便this引用它。

答案 1 :(得分:92)

Morhaus是正确的,但这可以在没有bind的情况下解决。

您可以将arrow functionclass properties proposal

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

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

因为箭头函数是在构造函数的范围内声明的,并且因为箭头函数从它们的声明范围维护this,所以它都可以工作。这里的缺点是这些不是原型上的函数,它们都将使用每个组件重新创建。然而,由于bind导致同样的事情,这并不是一个缺点。

答案 2 :(得分:47)

当从React.createClass()组件定义语法转换为扩展React.Component的ES6类方式时,此问题是我们大多数人遇到的第一个问题。

这是由thisReact.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

  5. 建议

    如果我是你,我会坚持使用方法#1。但是,只要在类构造函数中获得大量绑定,我就会建议您探索方法#4中提到的辅助库之一。

    其他

    它与您遇到的问题无关,但是您shouldn't overuse refs

      

    你的第一个倾向可能是使用裁判来使事情发生并且#34;在你的应用程序中如果是这种情况,请花一点时间,更关键地考虑组件层次结构中应该拥有哪个状态。

    出于类似目的,就像您需要的那样,使用controlled component是首选方式。我建议你考虑使用你的Component state。因此,您只需访问以下值:this.state.inputContent

答案 3 :(得分:2)

尽管前面的答案提供了解决方案的基本概述(即绑定,箭头功能,为您完成此任务的装饰器),但我还没有遇到一个答案,它实际上解释了为什么是必要的-我认为这是造成混乱的根源,并导致不必要的步骤,例如不必要的重新绑定和盲目地跟随他人的工作。

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对象上的foobar方法 1 所在的位置:

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

但是在第二步中,我们将方法存储到一个新变量中,并使用该变量来调用该方法,而没有明确说明该方法实际存在的位置,因此失去上下文

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

其中存在一个问题,当您将方法存储在变量中时,有关该方法所处位置(执行该方法的上下文)的原始信息会丢失。没有这些信息,在运行时,JavaScript解释器无法绑定正确的this -在没有特定上下文的情况下,this不能按预期 2 的方式工作。

与React相关

这是一个受this问题困扰的React组件(为简洁起见)的示例:

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 handles event handlers

// 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最终会分配给变量listener 3 。但是现在您看到了问题的出现-由于我们已将this.handleClick分配给listener,因此我们不再确切指定handleClick的来源!从React的角度来看,listener只是一些函数,没有附加到任何对象(在本例中为React组件实例)。我们丢失了上下文,因此解释器无法推断this值以使用 inside 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的{​​{1}}指向React组件实例,从而为您提供正确的this 4


1 我使用“方法”来指代应该绑定到对象的函数,而使用“方法”来指代不绑定到对象的函数。

2 在第二个代码段中,未定义记录而不是3,因为this默认为全局执行上下文(this不在严格模式下,否则为{{ 1}})无法通过特定上下文确定。并且在示例window中不存在,因此产生未定义的结果。

3 如果深入了解如何执行事件队列中的事件,则会在侦听器上调用invokeGuardedCallback

4 实际上要复杂得多。 React内部尝试在监听器上使用undefined以供自己使用,但这无法使用箭头功能,因为它们只是不绑定window.foo。这意味着,当实际评估箭头函数中的Function#apply时,this会在模块当前代码的每个执行上下文的每个词法环境中进行解析。最终解析为具有this绑定的执行上下文是构造函数,该构造函数的this指向当前的React组件实例,从而使其能够工作。

答案 4 :(得分: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

由于

答案 5 :(得分:1)

你可以通过三种方式解决这个问题

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

答案 6 :(得分:1)

我的建议是使用箭头功能作为属性

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)并在道具到达时进行浅层检查

答案 7 :(得分:1)

您可以按照以下步骤解决此问题

使用

更改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>

答案 8 :(得分:1)

我们必须用bind this来获得函数,以获取类中函数的实例。像这样

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

这样this.state将是有效的对象。

答案 9 :(得分:0)

如果有人能得到这个答案, 这是一种无需手动绑定所有功能即可绑定所有功能的方法

在constructor()中:

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

,然后在您的constructor()中将其命名为:

bindAllFunctions({ bindTo: this })

答案 10 :(得分:0)

bind(this)可以解决此问题,如今,如果您不喜欢使用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 doco

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来演示这些代码段,希望对您有所帮助。

答案 11 :(得分:0)

解决方案:

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

答案 12 :(得分:0)

亚历山大·基尔森伯格(Alexandre Kirszenberg)是正确的,但是要注意的另一重要事项是放置绑定的位置。我已经被这种情况困扰了好几天(可能是因为我是一个初学者),但是与其他人不同,我知道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"

答案 13 :(得分:0)

您的函数需要绑定才能与事件处理程序中的状态或道具一起玩

在ES5中,仅将事件处理函数绑定在构造函数中,而不直接在render中绑定。如果您确实直接在render中绑定,那么每次您的组件渲染并重新渲染时,它都会创建一个新函数。因此,您应该始终将其绑定在构造函数中

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

在ES6中,使用箭头功能

使用箭头功能时,无需绑定,也可以避免与范围相关的问题

sendContent = (event) => {

}

答案 14 :(得分:0)

您正在使用ES6,因此函数不会自动绑定到“ this”上下文。您必须手动将函数绑定到上下文。

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

答案 15 :(得分:0)

这个问题发生在react15.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不会因为新功能道具的改变而受到影响,或者可能会产生性能问题。

答案 16 :(得分:0)

如果您想在构造函数语法中保留bind,可以使用proposal-bind-operator并转换代码,如下所示:

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

而不是:

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

更简单,不需要bind(this)fatArrow

答案 17 :(得分:0)

您好,如果您不想关心绑定自己的函数调用。你可以使用'class-autobind'并像那样导入它

<ListView ItemsSource="{Binding Items}">
          <behaviors:Interaction.Behaviors>
            <behaviors:BehaviorCollection>
              <behaviors:EventToCommand
                  BindingContext="{behaviors:RelativeContext MPage}"
                  EventName="ItemTapped"
                  CommandName="OnItemSelected"
                  PassEventArgument="True"/>
            </behaviors:BehaviorCollection>
          </behaviors:Interaction.Behaviors>
    </ListView>

在超级调用之前不要写autobind因为它不起作用

答案 18 :(得分:0)

此问题正在发生,因为this.changeContentonClick={this.sendContent}未绑定到组件实例的

还有另一个解决方案(除了在构造函数()中使用bind())使用ES6的箭头函数,它们共享周围代码的相同词法范围并维护 this ,所以您可以将render()中的代码更改为:

render() {
    return (

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

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

    )
  }