57

我将 ReactJS 与 Babel 和 Webpack 一起使用,并使用 ES6 以及建议的箭头函数类字段。我知道箭头函数通过不重新创建每个渲染的函数来提高效率,类似于构造函数中的绑定工作方式。但是,我不能 100% 确定我是否正确使用它们。以下是我在三个不同文件中的代码的简化部分。

我的代码:

主.js

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

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

SecondClass.js

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

第三类.js

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

问题:

我上面的代码是否正确使用了箭头功能?我注意到对于 SecondClass.js 我也可以使用:

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

由于我在原始函数定义中使用了 ES6 箭头函数,因此一种方法或另一种方法有区别吗?或者我应该一直使用箭头语法,直到我的最后一个 div?

4

5 回答 5

63

我知道箭头函数通过在每次引用它们时不重新创建函数来提高效率

不是真的

箭头函数this以词法方式处理上下文,其中“普通”函数动态处理。如果您需要有关它的更多信息,我会深入介绍这个关键字。

在您的两个内联箭头函数示例中,您都在每个render.
这将在每个渲染上创建并传递一个新实例

onClick={() => {}}

在第三个示例中,您只有一个实例。
这仅传递对已存在实例的引用

onClick={this.myHandler}


至于箭头函数作为类字段的好处(有一个小的缺点,我会在答案的底部发布),如果您有一个需要访问当前实例的普通函数处理class程序this

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

您需要将其显式bindclass.
最常见的方法是在 中执行它,constructor因为它只运行一次:

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

但是,如果您使用箭头函数作为处理程序,则不需要bind它,class因为如上所述,箭头函数使用词法上下文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>

更好的方法是创建组件组合。
您可以创建一个包装相关标记的子组件,将拥有它自己的处理程序,并将从父 组件获取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>

类字段的缺点
正如我所提到的,类字段有一个小的缺点。
类方法和类字段的区别在于,类字段是附加到instanceclass构造函数)的。
其中类方法和对象附加到原型。

因此,如果您将拥有大量此类的实例,您可能会受到性能影响。

鉴于此代码块:

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;
}();
于 2018-02-09T07:14:18.510 回答
62

我知道箭头函数通过不重新创建每个渲染的函数来提高效率,类似于构造函数中的绑定工作方式。

这不是真的。这取决于您使用箭头功能的确切位置。如果Arrow function在 render 方法中使用,那么它们会创建一个新实例everytimerender 被调用,就像bind工作方式一样。考虑这个例子

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

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

但是考虑下面的情况

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

在上述情况下,箭头函数不会每次都重新创建函数,而是在An arrow function does not have its own this; the this value of the enclosing execution context is used. 实例化类时将上下文绑定到 React 组件。这类似于如何binding works is constructor。这是 ES6 特性的一部分,proposed class fields for arrow functions但不是 ES6 特性,

要理解你想问什么,你必须知道一个函数从它被调用的地方获取它的上下文。检查this question以了解更多信息。

在您的情况下,您已经习惯Arrow function了定义prevItem,因此它获取了封闭 React 组件的上下文。

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

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

现在在它的子节点中,即使您prevItem使用任何自定义上下文调用using bind or arrow functionprevItem当​​在父节点 ie 中执行时,Main.js也会获得其封闭 React 组件的上下文。而且由于您只想执行 prevItem 函数并且不想将任何数据从孩子传递给它,因此编写

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

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

根本没用,只会增加性能影响,因为每次都会创建新SecondClass功能ThirdClass。您根本不需要将这些函数定义为箭头函数,只需编写

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

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

因为它已经绑定在父级中。

现在即使你必须从 ThirdClass 和 SecondClass 向这些函数传递一些额外的数据,你也不应该直接使用Arrow functionor bind in render。看看这个答案How to Avoid binding in Render method

于 2018-02-12T06:50:56.323 回答
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 箭头函数,并且在上述各个场景中都是正确的

于 2018-02-09T06:29:00.463 回答
5

使用JavaScriptcurring函数声明,可以与其他答案不同,注意以下代码:

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

现在JSX,您可以编写:

<div onClick={this.clickHandler('someData')} />

clickHandlerwithsomeData返回一个带参数的函数,但e它不在clickHandler函数内部使用。所以它运作良好。

为了写得更完整,如下所示:

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

没有必要e,所以我为什么要写它。

于 2018-11-30T13:20:51.533 回答
2

在原始函数定义中使用箭头允许您不绑定构造函数中的函数。

如果你不使用箭头...

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

然后你必须创建一个构造函数并在那里绑定它......

class MyComponent extends Component {
  constructor(props) {
    super(props)
    this.prevItem = this.prevItem.bind(this)
  }

  prevItem() { ... }
}

开始时使用箭头会更容易,因为它可以正常工作,并且您不必了解构造函数是什么并深入研究thisjavascript 中的复杂性。

然而,在性能方面,最好在构造函数中绑定。构造函数中的绑定方法将创建函数的单个实例并重新使用它,即使渲染方法被多次调用。

于 2018-02-09T06:22:26.300 回答