尽管前面的答案提供了解决方案的基本概述(即绑定、箭头函数、为您执行此操作的装饰器),但我还没有找到一个答案实际上解释了为什么这是必要的——我认为这是根本混乱,并导致不必要的步骤,例如不必要的重新绑定和盲目跟随别人的做法。
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
方法1的位置——在foobar
对象上:
foobar.bar();
^^^^^^
但是在第二种情况下,我们将方法存储到一个新变量中,并使用该变量调用该方法,而没有明确说明该方法实际存在的位置,从而丢失了上下文:
barFunc(); // Which object is this function coming from?
这就是问题所在,当您将方法存储在变量中时,有关该方法所在位置(执行该方法的上下文)的原始信息会丢失。没有这些信息,在运行时,JavaScript 解释器无法绑定正确的this
- 没有特定的上下文,this
无法按预期工作2。
与反应有关
这是一个遇到问题的 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
最终会分配给变量listener
3。但是现在你看到问题出现了——因为我们已经分配this.handleClick
到listener
,我们不再指定确切handleClick
的来自哪里!从 React 的角度来看,listener
它只是一些函数,没有附加到任何对象(或者在这种情况下,是 React 组件实例)。我们失去了上下文,因此解释器无法推断要在 insidethis
中使用的值。 handleClick
为什么绑定有效
您可能想知道,如果解释器this
在运行时决定值,为什么我可以绑定处理程序以使其正常工作?这是因为您可以使用Function#bind
来保证运行时的this
值。这是通过在函数上设置内部this
绑定属性来完成的,允许它不推断this
:
this.handleClick = this.handleClick.bind(this);
当这一行被执行时,大概在构造函数中,当前this
被捕获this
(React 组件实例)并设置为一个全新函数的内部绑定,从Function#bind
. 这确保this
在运行时计算时,解释器不会尝试推断任何内容,而是使用this
您提供的值。
为什么箭头函数属性有效
箭头函数类属性目前通过 Babel 基于 transpilation 工作:
handleClick = () => { /* Can use this just fine here */ }
变成:
constructor() {
super();
this.handleClick = () => {}
}
这之所以有效,是因为箭头函数不绑定它们自己的 this,而是采用this
它们的封闭范围。在本例中,constructor
'sthis
指向 React 组件实例——从而为您提供正确的this
. 4
1我使用“方法”来指代应该绑定到对象的函数,而使用“函数”来指代那些没有绑定的函数。
2在第二个片段中,记录了 undefined 而不是 3,因为当无法通过特定上下文确定时,this
默认为全局执行上下文(window
非严格模式时,否则)。undefined
并且在示例window.foo
中不存在因此产生未定义。
3如果您深入了解事件队列中的事件是如何执行的,invokeGuardedCallback
则在侦听器上调用。
4这实际上要复杂得多。React 在内部尝试将Function#apply
监听器用于自己的用途,但这不起作用箭头函数,因为它们根本不绑定this
。这意味着,当this
实际评估箭头函数内部时,将this
解析模块当前代码的每个执行上下文的每个词法环境。最终解析为this
绑定的执行上下文是构造函数,它this
指向当前的 React 组件实例,允许它工作。