React:是推荐使用箭头函数还是普通函数?

5

我开始使用箭头函数是因为手动绑定函数/对象和涉及作用域的问题令人头痛,但最近我了解到在React中使用普通函数(ES5)比使用箭头函数(ES6)更好。

我的理解:

React中的普通函数:

  1. 手动绑定对象/函数以便在函数内部处理状态或属性,并避免作用域相关问题
  2. 总是在构造函数中绑定对象/函数,而不是直接在渲染中绑定
  3. 如果在构造函数中绑定,则Webpack只会在组件第一次渲染时创建新的对象/函数并将其放入bundle.js文件中
  4. 如果直接在渲染中绑定,则Webpack会在每次组件渲染和重新渲染时都创建一个新的对象/函数并将其放入bundle.js文件中
  5. 如果不绑定,则无法访问状态或属性。您必须将当前对象分配给本地变量,否则this.state或this.props为undefined

React中的箭头函数:

  1. 不需要在构造函数或渲染中绑定对象/函数
  2. 您无需依赖于当前对象的局部变量,即let that = this;
  3. 您将不会遇到作用域问题,并且对象/函数绑定会自动进行

但我的问题是,我听说推荐使用普通函数并在构造函数中绑定它,而不是使用箭头函数,因为箭头函数每次组件渲染和重新渲染时都会在Webpack bundle.js中创建新的对象/函数。

这是真的吗?哪个更好?

这个线程的被接受的答案React中箭头函数的正确使用说 -> 它取决于您在何处使用箭头函数。如果箭头函数在render方法中使用,则它们会像绑定一样在每次调用render时创建一个新实例。

如果您觉得这是一场戏剧性的问题,对不起。这是我最大的疑问,请给出建议。


2
我之前没有听说过反对箭头函数的这个论点,你能指出一个发出这个建议的来源吗? - kristaps
我在几天前回答stackoverflow时看到过这个问题的答案,但不太记得具体来源了。 - Hemadri Dasari
我认为这实际上并不重要。 - maxadorable
1
@auriga 这很重要,因为每当Webpack在bundle.js文件中创建新的对象/函数时,您的bundle.js文件大小就会增加,这就是为什么我们不建议在使用普通函数时直接在渲染中进行绑定的原因。因此,记住这一点,我听说箭头函数总是在组件重新渲染时创建一个新的对象.函数。这就是我的疑惑所在。 - Hemadri Dasari
3
这取决于你如何使用它们以及在哪里使用它们。如果你在事件处理程序属性内部创建它,无论是箭头函数还是普通函数,它都将被重新创建。如果你使用 ESNext 中的箭头函数作为类属性,它们将像传统的类方法一样附加到 this 上,并且只会被创建一次。 - Andrew Li
显示剩余2条评论
2个回答

11

很多人在这方面的回答都会让人感到困惑,我知道这一点是因为我曾经也有过困惑。不过过了一段时间后,我就掌握了相关概念。

  1. 手动绑定对象/函数以便在函数内部使用状态或属性并避免与作用域相关的问题

这并不完全正确。您不需要绑定函数来使用状态或属性,而是在失去this上下文的作用域中将函数绑定到this。例如,在回调函数中。

class App extends React.Component {
  state = {
    name: "foo",
  }
  aFunction() {
    console.log( this.state.name );
  }
  render() {
    return <div>{this.aFunction()}</div>;
  }
}

你不需要绑定你的函数,因为 this 指向你的类,你不会失去它的上下文。但如果你在回调函数中使用你的函数,比如一个按钮,你必须要绑定它:

class App extends React.Component {
  state = {
    name: "foo",
  }
  aFunction() {
    console.log( this.state.name );
  }

  render() {
    return (
      <div>
        <button onClick={this.aFunction}>Click</button>
      </div>
    );
  }
}

这样做是行不通的,因为你失去了上下文。现在,你需要以某种方式恢复上下文,对吧?好的,让我们看看如何做到这一点。首先,我想在按钮回调函数中绑定它。

<button onClick={this.aFunction.bind(this)}>Click</button>

是的,这样可以工作。但是,在每次渲染中都会重新创建它。所以:

  1. 在构造函数中绑定对象/函数,而不是直接在渲染中绑定

是的。不要像我上面那样绑定它,在构造函数中绑定它。

  1. 如果在构造函数中执行此操作,则Webpack仅在组件首次渲染时创建新的对象/函数存储在bundle.js文件中

  2. 如果直接在render中执行,则Webpack将在每次组件渲染和重新渲染时都创建一个新的对象/函数存储在bundle.js文件中

在这里总结了我到目前为止试图解释的内容。但是,我想Webpack不是在做这个,你的App才是。

  1. 如果不绑定,则无法访问state或props。您必须将当前对象分配给本地变量,否则this.state或this.props为undefined

同样,如果您在类范围内使用函数,则不必将其绑定。如果您在类外部使用此函数,例如按钮回调,则必须将其绑定。这与 stateprops 无关,而是与使用 this 有关。

绑定的第二个选项是在构造函数中使用普通函数进行绑定,第三个选项是使用箭头函数而不进行绑定。

现在,谈论箭头函数。

1. 不需要在构造函数或渲染中绑定对象/函数

是的。

  1. 您不需要依赖于当前对象的本地变量,即 let that = this;

是的。

  1. 您将不会遇到作用域问题,并且对象/函数绑定会自动进行

是的。

但我的疑问是,我听说推荐使用普通函数并在构造函数中进行绑定,而不是使用箭头函数,因为箭头函数每次组件渲染和重新渲染时都会在Webpack bundle.js中创建新的对象/函数。

像大家说的那样,这取决于您在哪里使用它们。

render() {
    return (
        <div>
            <button onClick={() => this.aFunction()}>Click</button>
        </div>
    );
}

这里,它将在每次渲染时重新创建。但是如果你不需要将任何参数传递给它,你可以通过引用来使用它。

render() {
    return (
        <div>
            <button onClick={this.aFunction}>Click</button>
        </div>
    );
}

这与前一个函数相同。因此,如果您在渲染方法中看到(),则该函数会在每次渲染时被重新创建。无论是常规函数还是箭头函数都一样。如果您以某种方式调用它,则正在重新创建它。这也适用于像aFunction.bind(this)这样在渲染中绑定的函数。我在那里看到()

因此,请通过引用使用函数来避免此问题。现在,重要的问题是当我们需要一些参数时会发生什么?如果您使用箭头函数传递参数,则请尝试更改逻辑。

但它真的很重要吗?就像@Eric Kim所说,仅当您确实需要优化时才是问题。这是一个普遍建议,因为我从许多人那里听到过这个建议。但就个人而言,如果它们将在每次渲染时被重新创建,我会尽量避免使用函数。但再次强调,这完全是个人意见。

如何更改逻辑?您正在映射包含项目的数组,并创建一个按钮。在此按钮中,您正在使用一个函数将项目的名称传递给另一个函数。

{
    items.map( item =>
        <button onClick={() => this.aFunction(item.name)}>Click</button>
    )
}

这个函数将在每次渲染时为每个项目重新创建!所以,请改变你的逻辑,创建一个独立的Item组件并映射它。将itemaFunction作为属性传递。然后在这个组件中使用一个处理函数来调用你的功能。

const Item = ( props ) => {
    const handleClick = () => props.aFunction( props.item.name );
    return (
        <button onClick={handleClick}>Click</button>
    );
}

在这里,您正在使用一个带有其引用的onClick处理程序,并调用了您的真实函数。每次渲染都不会重新创建函数。但是,作为缺点,您需要编写一个单独的组件和稍微多一点的代码。

大多数情况下,您可以应用此逻辑。也许会有一些例外,谁知道呢?所以决定权在您手中。

顺便说一句,在评论中@widged提供的Medium文章是关于这个问题的著名讨论。箭头函数真的比常规函数慢吗?是的。但是到底有多慢呢?我想不是很慢。此外,这对转译代码来说是正确的。将来当它们成为本地功能时,它们将会更快。

作为个人附注。我一直在使用箭头函数,因为我喜欢它们。但是一段时间前在一次讨论中,有人说过:

当我在类中看到箭头函数时,我认为:'此函数正在被用于/调用于这个类之外'. 如果我看到一个普通函数,我理解这个函数是在类内部调用的。

我真的很喜欢这种方法,现在如果我不需要在我的类之外调用函数,我就使用一个普通函数。


1
太棒了。你解决了我很多疑惑。非常感谢。伙计,讲解得真好。感谢你花费宝贵的时间,你的答案现在对我来说真的很有价值 :) - Hemadri Dasari
你在这篇文章中非常自由地使用了“作用域”这个词。我认为你要找的是“上下文”。当你在生命周期方法或其他绑定方法中专门使用this时,this上下文就是组件。你需要绑定的原因是,当你通过事件处理程序将函数作为一等对象传递时,this上下文会丢失(因为你不再调用this.someMethod)。 - Andrew Li
@Li357,你说得完全正确。这是我的错,措辞不当。范围是存在的,我们确实失去了上下文。通过像你在这里所做的更正,我会更好地学习术语。我已经更新了我的答案,非常感谢你。但是,我有点困惑:“当您在生命周期方法或其他绑定方法中专门使用此方法时,此上下文是组件。”如果该方法不是绑定方法,并且我在类内部调用它,this上下文仍然不是组件本身吗? - devserkan
@devserkan 嗯,这取决于情况。React明确地将this绑定到其生命周期方法。至于其他类方法,则取决于您如何调用它们以及是否自己提供上下文。如果您提供了上下文,那么this将是组件,但如果没有(即当React在内部调用事件处理程序时),则不会提供。 - Andrew Li
我理解生命周期方法的情况。对于其他方法,例如只记录状态变量的方法,我会在componentDidMount中调用它。无论是否绑定,这里的上下文都是类本身,对吧?你的“一些其他绑定方法”这句话让我有点困惑,就是这样。 - devserkan
啊!既然生命周期方法已经绑定了,而我们正在从其中一个方法调用我们的方法,那么这个方法已经被绑定了!或者不需要再次绑定,对吧? - devserkan

1
如果你有以下的React代码,
class A extends React.Component {
  constructor(props) {
    super(props)
    this.state = {name: props.name}
  }
  render() {
    return (
       <button onclick={(event)=>console.log(this.state.name)} />
    )
  }
}

改为以下内容。
class A extends React.Component {
  state = {name: this.props.name}
  render() {
    return (
       <button onclick={this.logName} />
    )
  }
  logName = (event) => {
    console.log(this.state.name)
  }
}

这样,您就不会在每次渲染时创建新的匿名函数。
每当运行代码()=>时,都会创建一个新的函数实例,这并不是魔法。请查看以下成员函数。
class A {
  memberFuncWithBinding = () => {}
  memberFuncWithoutBinding(){}
}

这两个成员函数只在类实例化时创建一次。没有什么魔法,但是更喜欢使用上面的成员函数,因为在该函数内部使用 this 时,您将具有正确的类 A this 绑定。

编辑:不要在遇到问题之前尝试优化代码。每次渲染创建新函数会慢一些,但只慢了几毫秒。


谢谢。请查看此线程的已接受答案 https://dev59.com/3VYM5IYBdhLWcg3wjAkR - Hemadri Dasari
您是想要更多澄清还是将此问题标记为重复? - Eric Kim
4
请注意,箭头函数(属性初始化器)选项仍处于实验阶段,不是ES6的一部分。您可以在react问题中找到有关此主题的进一步讨论-- https://github.com/facebook/react/issues/9851。个人建议尽可能避免使用箭头函数作为属性初始化器。我喜欢使用箭头函数来使我的代码尽量不引用this...而且在特定情况下,箭头函数引用了this,这也是我有问题的原因。 - widged
1
为了完整起见,这是箭头函数作为属性初始化器在转译后的样子:https://medium.com/@charpeni/arrow-functions-in-class-properties-might-not-be-as-great-as-we-think-3b3551c440b1。 - widged
1
感谢Eric Kim和widged花费宝贵的时间来解释这个概念 :) - Hemadri Dasari
显示剩余8条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接