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

IT技术 javascript reactjs ecmascript-6 babeljs
2021-01-19 00:20:13

我正在用 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
6个回答

this.changeContentthis.changeContent.bind(this)在作为onChangeprop传递之前需要绑定到组件实例 via否则this函数体中的变量将不会引用组件实例而是指向window. 请参阅函数::绑定

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

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

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

对比

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

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

在 ES6 中,定义在类上的方法不会自动绑定到实例。这就是为什么,当您需要时,您需要手动绑定它们。使用 BabelJS,使用属性初始化器语法和箭头函数,您可以直接定义将自动绑定到实例的方法。myMethod = () => ...而不是myMethod() { ... }.
2021-03-28 00:20:13
对不起,但我不明白为什么 this.changeContent需要通过this.changeContent.bind(this). 我的意思是,我们通过子类或 React.Component 编写组件,在 ES6 中,类中定义的每个方法都会自动绑定到通过子类/类本身创建的实例。为什么在这里我们需要“手动”完成?React 有什么特别之处吗?还是我对 ES6 类方法的动态感到厌烦?
2021-03-29 00:20:13
@marco这是一个不同的例子当您使用 调用方法时object.method()this体内变量method将引用object但是如果传递object.method给另一个函数,则只会传递函数本身的值,并且会丢失object上下文。这就是为什么在 React 中,有时您需要在将事件处理程序传递给组件之前手动绑定事件处理程序,以免丢失当前组件实例的上下文。
2021-04-01 00:20:13
@AlexandreKirszenberg 看看这个例子:该方法似乎自动绑定到实例...
2021-04-03 00:20:13
在构造函数本身中绑定函数以防止多次创建函数是一件好事
2021-04-09 00:20:13

Morhaus 是正确的,但是没有bind.

您可以将箭头函数类属性建议一起使用

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

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

因为箭头函数是在构造函数的作用this域中声明的,并且因为箭头函数从它们的声明作用域维护,所以一切正常。这里的缺点是这些不会是原型上的函数,它们将在每个组件中重新创建。然而,这并不是什么坏处,因为bind结果是一样的。

这在 TypeScript 中也很有效(通常不必担心在 TypeScript 中绑定,但我想这是不同的)
2021-03-12 00:20:13
这不起作用。我得到“属性声明只能在 .ts 文件中使用”
2021-03-14 00:20:13
构造函数不会破坏此代码,您必须有另一个问题。也许你没有正确的插件?这不是 2015 预设的一部分,它被称为babel-plugin-transform-class-properties. 如果您向我展示您的代码,我可以告诉您问题是什么。babel repl 为您提供了一个很好的可共享链接。
2021-03-15 00:20:13
可能是我设置了一个构造函数,但除此之外,我的示例是相同的并且无法编译。绑定方法虽然有效。
2021-03-29 00:20:13
@BHouwens这是在 babel REPL 中我不知道你在做什么,但你做错了什么。
2021-04-09 00:20:13

这个问题是我们大多数人在从React.createClass()组件定义语法转换到 ES6 类扩展React.Component.

这是由vs 中this上下文差异引起的React.createClass()extends React.Component

UsingReact.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,即使方法是分离的。该包使用@autobindbefore 方法绑定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 中提到的帮助程序库之一。


其他

它与您遇到的问题无关,但您不应过度使用 refs

您的第一个倾向可能是使用 refs 在您的应用程序中“使事情发生”。如果是这种情况,请花点时间更批判性地思考状态应该在组件层次结构中的哪个位置拥有。

出于类似目的,就像您需要的那样,使用受控组件是首选方式。我建议您考虑使用您的Componentstate所以,你可以简单地访问这样的值:this.state.inputContent

@Tyrsius,它就在那里。请参阅我的答案中的方法 #3,一个粗箭头函数 + 类属性提议。
2021-03-13 00:20:13
这比公认的答案更完整和有用。
2021-03-25 00:20:13
这缺少其他答案中的方法stackoverflow.com/a/34050078/788260
2021-04-07 00:20:13
@KaloyanKosev 如果不是单击操作而只是简单的方法调用怎么办?
2021-04-09 00:20:13

尽管之前的答案已经提供了解决方案的基本概述(即绑定、箭头函数、为您执行此操作的装饰器),但我还没有找到真正解释为什么这是必要的答案——在我看来这是根本混乱,并导致不必要的步骤,例如不必要的重新绑定和盲目跟随他人的做法。

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!

可能会意外地发现它记录了 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有关

这是一个遇到问题的 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.handleClicklistener,我们不再具体指定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 ,而是占用this它们封闭范围的the 在这种情况下,指向 React 组件实例constructor's this,从而为您提供正确的this. 4


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

2在第二个片段中,记录 undefined 而不是 3,因为当无法通过特定上下文确定时,this默认为全局执行上下文(window如果不是严格模式,否则为 else undefined)。并且在示例window.foo中不存在因此产生未定义。

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

4它实际上要复杂得多React 内部尝试将Function#apply侦听器用于自己的用途,但这不起作用箭头函数,因为它们根本不绑定this这意味着,当this实际评估箭头函数时,this会解析module当前代码的每个执行上下文的每个词法环境。最终解析为具有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