React中箭头函数的正确使用方法

62
我正在使用ReactJS,Babel和Webpack,并使用ES6以及建议的类字段用于箭头函数。我知道箭头函数通过不在每次渲染时重新创建函数使事情更有效率,类似于构造函数中的绑定方式。但是,我不确定我是否正确地使用它们。以下是我的代码的简化部分,在三个不同的文件中。

Main.js

prevItem = () => {
    console.log("Div is clicked")
}

render(){
    return (
         <SecondClass prevItem={this.prevItem} />
    )
}

SecondClass.js

<ThirdClass type="prev" onClick={()=>this.props.prevItem()} />

ThirdClass.js

<div onClick={()=>{this.props.onClick()}}>Previous</div>

问题:

我上面的代码是否正确地使用了箭头函数?我注意到对于SecondClass.js,我也可以使用以下语法:

<ThirdClass type="prev" onClick={this.props.prevItem} />

如果我在原始函数定义中使用了ES6箭头函数,那么在使用另一种方法时是否有区别?或者我应该一直使用箭头语法直到最后一个div?


请点击此链接查看有关箭头函数的更多详细信息:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/Arrow_functions - Zi Gang
由于prevItem中没有对this的引用,因此您可以使用它。但是,如果您添加作用域级别的代码,它将会失效。因为您正在从一个对象中分配并调用函数,所以它将失去其上下文。onClick={this.props.prevItem} - Rajesh
3
了解,类字段不是 ES6 的一部分。 - Felix Kling
5个回答

67
我知道箭头函数通过避免每次引用时重新创建函数,使事情更有效率。但这不是真的。箭头函数以词法方式处理this上下文,而“普通”函数则动态地处理它。如果您需要更多信息,请查看我详细介绍this关键字的文章。在这里
在您提到的内联箭头函数示例中,每次render都会创建一个新的函数实例。这将在每个渲染过程中创建并传递一个新的实例。
onClick={() => {}}

在第三个例子中,您只有一个实例。
这只传递到已经存在的实例的引用。

onClick={this.myHandler}


关于将箭头函数作为类属性的好处(有一个小缺点,我会在回答底部发布它),如果您有一个需要通过this访问当前class实例的普通函数处理程序:

myHandler(){
  //  this.setState(...)
}

您需要将其显式地绑定
最常见的方法是在constructor中执行,因为它仅运行一次:

您需要将其显式地绑定
最常见的方法是在constructor中执行,因为它仅运行一次:

constructor(props){
  super(props);
  this.myHandler = this.myHandler.bind(this);
}

如果您将箭头函数用作处理程序,那么您不需要将它绑定到类上,因为如上所述,箭头函数使用词法上下文来确定 this:

如果您将箭头函数用作处理程序,则无需将其绑定到类上,因为如上所述,箭头函数使用词法上下文来确定 this:

myHandler = () => {
  //  this.setState(...)
}

使用这两种方法,您将像这样使用处理程序:

<div onClick={this.myHandler}></div> 

采用这种方法的主要原因:

<div onClick={() => this.myHandler(someParameter)}></div>

如果你想在处理程序中传递参数而不是仅传递原生的event,也就是说,你想向上传递一个参数。
正如提到的那样,这将在每次渲染时创建一个新的函数实例。
(有更好的方法,继续阅读)。
针对这种用例的运行示例:

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      items: [{ name: 'item 1', active: false }, { name: 'item 2', active: true }],
    }
  }
  toggleITem = (itemName) => {
    this.setState(prev => {
      const nextState = prev.items.map(item => {
        if (item.name !== itemName) return item;
        return {
          ...item,
          active: !item.active
        }
      });
      return { items: nextState };
    });
  }
  render() {
    const { items } = this.state;
    return (
      <div>
        {
          items.map(item => {
            const style = { color: item.active ? 'green' : 'red' };
            return (
              <div
                onClick={() => this.toggleITem(item.name)}
                style={style}
              >
                {item.name}
              </div>
          
          )})
        }
      </div>
    );
  }
}

ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>

更好的方法是创建组件组合。 您可以创建一个子组件来包装相关的标记,它将拥有自己的处理程序,并从父组件中作为props获取data和handler。
然后,子组件将调用从父组件获取的处理程序,并将data作为参数传递。
使用子组件运行示例:

class Item extends React.Component {
  onClick = () => {
    const { onClick, name } = this.props;
    onClick(name);
  }
  render() {
    const { name, active } = this.props;
    const style = { color: active ? 'green' : 'red' };
    return (<div style={style} onClick={this.onClick}>{name}</div>)
  }
}

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      items: [{ name: 'item 1', active: false }, { name: 'item 2', active: true }],
    }
  }
  toggleITem = (itemName) => {
    this.setState(prev => {
      const nextState = prev.items.map(item => {
        if (item.name !== itemName) return item;
        return {
          ...item,
          active: !item.active
        }
      });
      return { items: nextState };
    });
  }
  render() {
    const { items } = this.state;
    return (
      <div>
        {
          items.map(item => {
            return <Item {...item} onClick={this.toggleITem} />
          })
        }
      </div>
    );
  }
}

ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>

Class Fields the down-side:
正如我所提到的,类字段存在一些小的缺点。
类方法和类字段之间的区别在于,类字段附加到(构造函数)的实例上,而类方法和对象则附加到原型上。

因此,如果您将有非常大量的此类实例,则可能会导致性能下降。

给定这段代码块:

class MyClass {
  myMethod(){}  
  myOtherMethod = () => {}
}

Babel 将其转换为:

var _createClass = function() {
  function defineProperties(target, props) {
    for (var i = 0; i < props.length; i++) {
      var descriptor = props[i];
      descriptor.enumerable = descriptor.enumerable || false;
      descriptor.configurable = true;
      if ("value" in descriptor) descriptor.writable = true;
      Object.defineProperty(target, descriptor.key, descriptor);
    }
  }
  return function(Constructor, protoProps, staticProps) {
    if (protoProps) defineProperties(Constructor.prototype, protoProps);
    if (staticProps) defineProperties(Constructor, staticProps);
    return Constructor;
  };
}();

function _classCallCheck(instance, Constructor) {
  if (!(instance instanceof Constructor)) {
    throw new TypeError("Cannot call a class as a function");
  }
}

var MyClass = function() {
  function MyClass() {
    _classCallCheck(this, MyClass);

    this.myOtherMethod = function() {};
  }

  _createClass(MyClass, [{
    key: "myMethod",
    value: function myMethod() {}
  }]);

  return MyClass;
}();

React 表示这并不属实(关于第一个例子“在每次渲染时创建新的函数实例”的说法)。https://reactjs.org/docs/faq-functions.html - diesel94
@diesel94 你确定吗?我从你发的链接中读到的内容几乎和我写的一模一样:在render中使用箭头函数会在每次组件渲染时创建一个新的函数,这可能会破坏基于严格身份比较的优化。 - Sagiv b.g

67
我知道箭头函数通过不像在构造函数中绑定一样每次重新创建函数,从而使事情更加高效。
但实际上这并非完全正确。这取决于你在哪里使用箭头函数。如果在渲染方法中使用箭头函数,则它们会在每次调用渲染时创建一个新实例,就像绑定一样。请考虑以下示例。
<div onClick={()=>{this.onClick()}}>Previous</div>

每次调用render时,都会创建一个匿名函数,该函数在调用时调用this.onClick

但是考虑下面的情况

onClick = () => {
    console.log("Div is clicked")
}

在上述情况中,箭头函数不会每次重新创建函数,而是将上下文绑定到React组件,就像绑定构造函数一样。这是“建议的箭头函数类字段”的一部分,不是ES6功能。
要理解您想要问什么,您必须知道函数从调用它的位置获取其上下文。请查看this question以获得更多了解。
在您的情况下,您使用箭头函数定义了prevItem,因此它获取了封闭React组件的上下文。
prevItem = () => {
    console.log("Div is clicked")
}

render(){
    return (
         <SecondClass prevItem={this.prevItem} />
    )
}

现在在它的子组件中,即使您使用绑定或箭头函数来调用prevItem并传入任何自定义上下文,当在父组件即Main.js中执行prevItem时,它将获取其封闭React组件的上下文。由于您只想执行prevItem函数而不想从子组件向其传递任何数据,因此编写:
<ThirdClass type="prev" onClick={()=>this.props.prevItem()} />

并且

<div onClick={()=>{this.props.onClick()}}>Previous</div>

这样做毫无意义,只会增加性能问题,因为每次都会在SecondClassThirdClass中创建新的函数。其实你根本不需要将这些函数定义为箭头函数,直接编写即可。

<ThirdClass type="prev" onClick={this.props.prevItem} />

并且

<div onClick={this.props.onClick}>Previous</div>

由于它已经在父级中绑定。

现在,即使你需要从ThirdClass和SecondClass向这些函数传递一些额外的数据,你也不应该直接使用箭头函数在渲染中绑定。看看这个关于如何避免在Render方法中绑定的答案。


6
使用箭头函数本身并没有问题。即使在render中重复创建箭头函数,使用箭头函数也是完全可以的。在大多数应用程序中,性能差异不会明显。 - Divyanshu Maithani
@DivyanshuMaithani,上面的代码对于函数组件也适用吗?还是只适用于类组件?当使用函数组件时,我们需要像下面这样重新绑定它吗?SecondClass.js<ThirdClass type="prev" onClick={()=>this.props.prevItem()} />ThirdClass.js<div onClick={()=>{this.props.onClick()}}>Previous</div> - Ram Somani
我的评论是关于性能问题的。答案有点令人困惑,简而言之,您只需要绑定类方法(在类组件中)。对于功能组件,无需绑定函数,因为您不需要关注this的使用。 - Divyanshu Maithani

5
因此,您的第一种方法是
<ThirdClass type="prev" onClick={()=>this.props.prevItem()} />

在这里,您可以将ThirdClass中可用的任何参数传递给prevItem函数。这是一种通过参数调用父函数的好方法。就像这样:

<ThirdClass type="prev" onClick={()=>this.props.prevItem(firstArgument, secondArgument)} />

您的第二种方法是:
<ThirdClass type="prev" onClick={this.props.prevItem} />

这种方法不允许您传递任何ThirdClass特定的参数。

两种方法都是正确的,只是取决于您的使用情况。在上述各自的场景中,使用es6箭头函数的两种方法都是正确的。


5

使用 JavaScript 的柯里化函数声明,可以是一个不同于其他答案的方式,注意以下代码:

clickHandler = someData => e => this.setState({
  stateKey: someData
});

现在在 JSX 中,你可以这样写:
<div onClick={this.clickHandler('someData')} />
clickHandler带有someData参数,返回一个带有e参数的函数,但在clickHandler函数内部没有使用e参数。因此它可以正常工作。
为了更完整地写出来,可以像下面这样写:
clickHandler = someData => () => this.setState({
  stateKey: someData
});

不需要使用e,那我为什么还要写它呢。


2

在原始函数定义中使用箭头可以避免在构造函数中绑定函数。

如果您没有使用箭头...

prevItem(){
  console.log("Div is clicked")
}

然后您需要创建一个构造函数并将其绑定在那里...
class MyComponent extends Component {
  constructor(props) {
    super(props)
    this.prevItem = this.prevItem.bind(this)
  }

  prevItem() { ... }
}

使用箭头函数在开始时更容易,因为它可以直接使用,而不需要理解构造函数和深入了解javascript中的“this”复杂性。但是,从性能上来看,在构造函数中绑定更好。在构造函数中绑定方法将创建函数的单个实例并重用它,即使呈现方法被多次调用。

使用这种方法,是否有一种方式在调用prevItem时添加参数。例如:onpress={this.prevItem},但我想调用this.prevItem(variable) - tnaught

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