[翻译]在 React 中抛弃 .bind(this)

2,548 阅读4分钟
原文链接: codesky.me
原文:Losing .bind(this) in React

——在 React 组件中抛弃 .bind(this)

这将会成为历史。

假设你经常在使用 React,你或许会不得不写类似于 .bind(this) 的代码,当然,我知道:

  • 这看上去真的丑
  • 这在代码中占用了额外的空间

幸好,JavaScript 有一些提案中的特性可以让 .bind(this) 成为我们的过去。

在我解释如何抛弃 .bind(this) 之前,我将会给你看一段简单的例子来展示他可以被用在哪里。我们希望渲染一个点击了之后就能改变文本的按钮。为了实现这个效果,我们会写一个像下面差不多一个意思的组件:

import React, { Component } from "react";

class ButtonWithBind extends Component {
  constructor() {
    super();

    this.state = { toggle: false };
  }

  render() {
    const toggle = this.state.toggle;

    return (
      <div>
        <button onClick={this.toggleButton}>
          {toggle ? "ON" : "OFF"}
        </button>
      </div>
    );
  }

  toggleButton() {
    this.setState(prevState => ({ toggle: !prevState.toggle }));
  }
}

export default ButtonWithBind;

我们在构造器中中把 toggle 的开关状态设置为 false

此外,我们绑定了一个onClick 处理函数: toggleButton 函数,所以它会在按钮被点击的时候被调用。

我们也创造了一个简单的 toggleButton 函数,用以在被调用时改变状态。

棒极了,看上去我们准备好了。

如果我们直接去点击这个渲染出来的按钮,会得到一个下图一样的 TypeError

铛!它明明应该工作的🤔!

我们之所以得到一个错误是因为当 onClick 调用我们的 toggleButton 函数时,函数并没有定义。

通常,你会通过在 toggleButton 函数中绑定上 this 指针来修复这个问题,所以他看上去保持不变。让我们继续在构造函数上位函数上绑定上 this

this.toggleButton = this.toggleButton.bind(this);

添加上这个之后,我们的按钮组件看上去像这样:

import React, { Component } from "react";

class ButtonWithBind extends Component {
  constructor() {
    super();

    this.state = { toggle: false };

    this.toggleButton = this.toggleButton.bind(this);
  }

  render() {
    const toggle = this.state.toggle;

    return (
      <div>
        <button onClick={this.toggleButton}>
          {toggle ? "ON" : "OFF"}
        </button>
      </div>
    );
  }

  toggleButton() {
    this.setState(prevState => ({ toggle: !prevState.toggle }));
  }
}

export default ButtonWithBind;

试试吧,它应该可以正常运作了:

是的,没毛病。

🔪 .bind(this)

现在,让我们开始抛弃这个讨人厌的 .bind(this),为了做这个,我们将要使用 JavaScript 的实验中的公有类字段(public class fields)特性。公有类字段特性可以让你在你们的类中使用箭头函数语法。

toggleButton = () => { 
  this.setState(prevState => ({ toggle: !prevState.toggle }));
}

一个箭头函数没有它自己的 this,不过他使用的是封闭的执行上下文的 this 值。箭头函数在词法上绑定它们的上下文,所以 this 实际上指向最原始的上下文。如果你要进行命名,这也被叫做词法环境。

从根本上来说,这让我们省下了代码中的 .bind(this)

注意,这是 JS 中的一个实验中的特性,这意味着她还没有被 ECMAScript 的标准所采纳,不过让我们保持手指交叉,做个🤞。在它被采纳之前,你可以配置 babel,使用 babel-plugin-transform-class-properties 来转换它。

可能的陷阱

记住,这可能会影响两件事。第一件是内存和性能。当你使用类字段来定义一个函数时,你的方法将驻留在类的每个实例上,而不是在原型上,因为原本使用了 bind 方法。你可以通过阅读 Donavon West 的一篇精彩的文章来深入理解——Demystifying Memory Usage using ES6 React Classes

第二件事是通过使用公有类字段来影响你如何编写单元测试,你将不能使用组件原型来进行这样的函数打桩:

const spy = jest.spyOn(ButtonWithoutBind.prototype, 'toggleButton'); expect(spy).toHaveBeenCalled();

不将不得不寻找另一种方法来打桩方法,比如在 props 中传递 spy 或者检查状态的变化。

在组件中使用

现在,让我们跳到我们在组件中如何使用公有类字段,并改变我们的 toggleButton 函数来丢掉 .bind(this)


import React, { Component } from "react";

class ButtonWithoutBind extends Component {
  constructor() {
    super();

    this.state = { toggle: false };
  }

  render() {
    const toggle = this.state.toggle;

    return (
      <div>
        <button onClick={this.toggleButton}>
          {toggle ? "ON" : "OFF"}
        </button>
      </div>
    );
  }

  toggleButton = () => {
    this.setState(prevState => ({ toggle: !prevState.toggle }));
  }
}
每个 React 开发者都曾经说过:看着 22 - 24 行,哇,太漂亮了!没有更多讨厌的 .bind(this) 了。

公有类字段还有一个好处,就是我们可以从构造函数中定义状态,然后细化我们的组件:

import React, { Component } from "react";

class ButtonWithoutBind extends Component {
  state = { toggle: false }
  
  render() {
    const toggle = this.state.toggle;

    return (
      <div>
        <button onClick={this.toggleButton}>
          {toggle ? "ON" : "OFF"}
        </button>
      </div>
    );
  }

  toggleButton = () => {
    this.setState(prevState => ({ toggle: !prevState.toggle }));
  }
}

export default ButtonWithoutBind;

而且,我们已经丢掉了 .bind(this),我们已经细化一丢丢我们的组件,可以称之为胜利了🏁!我们应该得到某种奖励,随意的去冰箱散步,拿一个冷的🍺或者一个巧克力🍫,又或者是任何你喜欢的东西。因为你刚刚学到了可以用在 React 中的全新的东西。

非常感谢 Kent C. Dodds 为此制作的视频。这篇文章没有他就不会存在。干杯🍻 Kent。

如果你喜欢你所看到的,就请👏并传播这个文章。另外,看看我的网站follow 我,我将发布更多 React 相关的文章,所以点击 Follow 保持关注🎥。