在使用 React 时,您不免遇到受控组件和事件处理程序。在自定义组件的构造函数中,咱们须要使用 .bind() 来将方法绑定到组件实例上面。node
class Foo extends React.Component{ constructor( props ){ super( props ); this.handleClick = this.handleClick.bind(this); } handleClick(event){ // 你的事件处理逻辑 } render(){ return ( <button type="button" onClick={this.handleClick}> Click Me </button> ); } } ReactDOM.render( <Foo />, document.getElementById("app") );
在这篇文章中,咱们将探究为何要这么作。
若是你对 .bind()
尚不了解,推荐阅读 这篇文章。git
好吧,责怪听起来有些苛刻。若是按照 React
和 JSX
的语法,咱们并不须要这么作。其实绑定 this
是 JavaScript 中的语法。github
让咱们看看,若是不将事件处理程序绑定到组件实例上,会发生什么:babel
class Foo extends React.Component{ constructor( props ){ super( props ); } handleClick(event){ console.log(this); // 'this' 值为 undefined } render(){ return ( <button type="button" onClick={this.handleClick}> Click Me </button> ); } } ReactDOM.render( <Foo />, document.getElementById("app") );
若是你运行这个代码,点击 “Click Me” 按钮,检查你的控制台,你将会看到控制台打印出 undefined
,这个值是 handleClick()
方法内部的 this
值。handleClick()
方法彷佛已经丢失了其上下文(组件实例),即 this
值。app
正如我上文提到的,是 JavaScript 的 this
绑定机制致使了上述状况的发生。在这篇文章中,我不会深刻探讨太多细节,可是 这篇文章 能够帮助你进一步学习在 JavaScript 中 this
的绑定是如何工做的。函数
与咱们讨论相关的是,函数内部的 this
的值取决于该函数如何被调用。学习
function display(){ console.log(this); // 'this' 将指向全局变量 } display();
这是一个普通的函数调用。在这种状况下,display()
方法中的 this
在非严格模式下指向 window
或 global
对象。在严格模式下,this
指向 undefined
。this
var obj = { name: 'Saurabh', display: function(){ console.log(this.name); // 'this' 指向 obj } }; obj.display(); // Saurabh
当咱们以一个 obj
对象来调用这个函数时,display()
方法内部的 this
指向 obj
。prototype
可是,当咱们将这个函数引用赋值给某个其余变量并使用这个新变量去调用该函数时,咱们在 display()
中得到了不一样的this
值。code
var name = "uh oh! global"; var outerDisplay = obj.display; outerDisplay(); // uh oh! global (node)
在上面的例子里,当咱们调用 outerDisplay()
时,咱们没有指定一个具体的上下文对象。这是一个没有全部者对象的纯函数调用。在这种状况下,display()
内部的 this
值回退到默认绑定。如今这个 this
指向全局对象,在严格模式下,它指向 undefined。
在将这些函数以回调的形式传递给另外一个自定义函数、第三方库函数或者像 setTimeout
这样的内置JavaScript函数时,上面提到的判断方法会特别实用。
考虑下方的代码,当自定义一个 setTimeout
方法并调用它,会发生什么。
//setTimeout 的虚拟实现 function setTimeout(callback, delay){ //等待 'delay' 数个毫秒 callback(); } setTimeout( obj.display, 1000 );
咱们能够分析出,当调用 setTimeout
时,JavaScript 在内部将 obj.display
赋给参数 callback
。
callback = obj.display;
正如咱们以前分析的,这种赋值操做会致使 display()
函数丢失其上下文。当此函数最终在 setTimeout
函数里面被调用时,display()
内部的 this
的值会退回至默认绑定。
var name = "uh oh! global"; setTimeout( obj.display, 1000 ); // uh oh! global
为了不这种状况,咱们可使用 明确绑定方法,将 this 的值经过 bind()
方法绑定到函数上。
bind
方法返回一个新函数,其this
制定为bind
函数参数对象.
var name = "uh oh! global"; obj.display = obj.display.bind(obj); var outerDisplay = obj.display; outerDisplay(); // Saurabh
如今,当咱们调用 outerDisplay()
时,this
的值指向 display()
内部的 obj
。
即便咱们将 obj.display
直接做为 callback
参数传递给函数,display()
内部的 this
也会正确地指向 obj
。
在本文的开头,咱们建立了一个类名为 Foo
的 React 组件。若是咱们不将 this 绑定到事件上,事件内的值会变成 undefined
。
正如我上文解释的那样,这是由 JavaScript 中 this 绑定的方式决定的,与React的工做方式无关。所以,让咱们删除 React 自己的代码,并构建一个相似的纯 JavaScript 示例,来模拟此行为。
class Foo { constructor(name){ this.name = name } display(){ console.log(this.name); } } var foo = new Foo('Saurabh'); foo.display(); // Saurabh //下面的赋值操做模拟了上下文的丢失。 //与实际在 React Component 中将处理程序做为 callback 参数传递类似。 var display = foo.display; display(); // TypeError: this is undefined
咱们不是模拟实际的事件和处理程序,而是用同义代码替代。正如咱们在 React 组件示例中所看到的那样,因为将处理程序做为回调传递后,丢失了上下文,致使 this 值变成 undefined。这也是咱们在这个纯 JavaScript 代码片断中观察到的。
你可能会问:“等一下!难道 this 的值不是应该指向全局对象么,由于咱们是按照默认绑定的规则,在非严格模式下运行的它。“
答案是否认的 缘由以下:
类声明和类表达式的主体以 严格模式 执行,主要包括构造函数、静态方法和原型方法。Getter 和 setter 函数也在严格模式下执行。
你能够在 这里 阅读完整的文章。
因此为了不错误,咱们须要像下文这样绑定 this 的值:
class Foo { constructor(name){ this.name = name this.display = this.display.bind(this); } display(){ console.log(this.name); } } var foo = new Foo('Saurabh'); foo.display(); // Saurabh var display = foo.display; display(); // Saurabh
咱们不只能够在构造函数中执行此操做,也能够在其余位置执行此操做。考虑这个:
class Foo { constructor(name){ this.name = name; } display(){ console.log(this.name); } } var foo = new Foo('Saurabh'); foo.display = foo.display.bind(foo); foo.display(); // Saurabh var display = foo.display; display(); // Saurabh
但因为构造函数是全部初始化发生的地方,所以它是编写绑定事件语句最佳的位置。
在 React 组件内,咱们有另外两种定义事件处理程序的方式。
class Foo extends React.Component{ handleClick = () => { console.log(this); } render(){ return ( <button type="button" onClick={this.handleClick}> Click Me </button> ); } } ReactDOM.render( <Foo />, document.getElementById("app") ); // 注意 handleClick写法: // handleClick = () => {
class Foo extends React.Component{ handleClick(event){ console.log(this); } render(){ return ( <button type="button" onClick={(e) => this.handleClick(e)}> Click Me </button> ); } } ReactDOM.render( <Foo />, document.getElementById("app") );
这两个都使用了ES6引入的箭头函数。当使用这些替代方法时,咱们的事件处理程序已经自动绑定到了组件实例上,而且咱们不须要在构造函数中绑定它。
缘由是在箭头函数的状况下,this 是有词法约束力的。这意味它可使用封闭的函数上下文或者全局上下文做为 this 的值。
在公共类字段语法的例子中,箭头函数被包含在 Foo 类中或者构造函数中,因此它的上下文就是组件实例,而这就是咱们想要的。
在箭头函数做为回调的例子中,箭头函数被包含在 render() 方法中,该方法由 React 在组件实例的上下文中调用。这就是为何箭头函数也能够捕获相同的上下文,而且其中的 this 值将正确的指向组件实例。
在 React 的类组件中,当咱们把事件处理函数引用做为回调传递过去,以下所示:
<button type="button" onClick={this.handleClick}>Click Me</button>
事件处理程序方法会丢失其隐式绑定的上下文。当事件被触发而且处理程序被调用时,this的值会回退到默认绑定,即值为 undefined,这是由于类声明和原型方法是以严格模式运行。当咱们将事件处理程序的 this 绑定到构造函数中的组件实例时,咱们能够将它做为回调传递,而不用担忧会丢失它的上下文。箭头函数能够免除这种行为,由于它使用的是词法 this 绑定,会将其自动绑定到定义他们的函数上下文。