虽然之前的答案已经提供了解决方案的基本概述(即绑定、箭头函数、为您完成此操作的装饰器),但我仍然没有看到一个解释
为什么这是必要的,而我认为这是混淆的根源,导致不必要的步骤,如不必要的重新绑定和盲目地跟随他人的做法。
this
是动态的
为了理解这个特定的情况,需要简要介绍一下this
的工作原理。关键在于,this
是运行时绑定,取决于当前执行上下文。因此,它通常被称为“上下文”,提供有关当前执行上下文的信息,而您需要绑定的原因是因为您会失去“上下文”。但让我用一段代码片段来说明这个问题:
const foobar = {
bar: function () {
return this.foo;
},
foo: 3,
};
console.log(foobar.bar());
在这个例子中,我们得到了预期的
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()
在这里出现了问题,当你将一个方法存储在变量中时,关于该方法所在位置的原始信息(方法执行的上下文)就丢失了。没有这些信息,在运行时,JavaScript解释器无法绑定正确的
this
- 没有特定的上下文,
this
不能按预期工作
2。
与React相关
以下是一个React组件的示例(为简洁起见进行了缩短),它遭受了
this
问题的困扰:
handleClick() {
this.setState(({ clicks }) => ({
clicks: clicks + 1,
}));
}
render() {
return (
<button onClick={this.handleClick}>{this.state.clicks}</button>
);
}
但是为什么,以及前一节如何与此相关呢?这是因为它们遭受了同样问题的抽象。如果您看一下
React 如何处理事件处理程序:
let listener = props[registrationName];
因此,当你执行
onClick={this.handleClick}
时,方法
this.handleClick
最终被分配给变量
listener
3。但现在你看到问题出现了——由于我们将
this.handleClick
分配给了
listener
,我们不再明确指定
handleClick
来自哪里!从React的角度来看,
listener
只是一些函数,没有附加到任何对象(或在这种情况下,React组件实例)。我们失去了上下文,因此解释器无法推断要在
handleClick
内使用的
this
值。
为什么绑定起作用
你可能会想,如果解释器在运行时决定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。在这种情况下,它使用构造函数的this,该this指向React组件实例,从而给出了正确的this。
1 我使用“方法”一词来指代应该绑定到对象的函数,“函数”则指其他类型的函数。
2 在第二个片段中,打印出的是 undefined 而不是 3,因为当无法通过特定上下文确定时,this
默认为全局执行环境(非严格模式下为 window
,否则为 undefined
)。在这个例子中,window.foo
不存在,因此返回 undefined。
3 如果你深入研究事件队列中的事件如何执行,invokeGuardedCallback
将被调用以处理监听器。
4 实际上,这个问题更加复杂。React在内部尝试对其自身使用Function#apply
来监听事件,但箭头函数无法绑定this
,因此不起作用。这意味着,当箭头函数内的this
被实际评估时,this
会在当前模块的每个执行环境的每个词法环境中向上解析。最终解析到具有this
绑定的执行环境是构造函数,该构造函数具有指向当前React组件实例的this
,从而使其正常工作。
React.findDOMNode(React.refs.someref).value
改为this.state.inputContent
,并删除ref="someref"
。 - Brigand