React中箭头函数的正确使用

Correct use of arrow functions in React

我将 ReactJS 与 Babel 和 Webpack 一起使用,并使用 ES6 以及 proposed class fields for arrow functions. I understand that arrow functions make things more efficient by not recreating the functions each render 类似于构造函数中绑定的工作方式。但是,我不能 100% 确定我是否正确使用了它们。以下是我在三个不同文件中的代码的简化部分。

我的代码:

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?

在原始函数定义中使用箭头可以让您不在构造函数中绑定函数。

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

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

然后你必须创建一个构造函数并将其绑定到那里...

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

  prevItem() { ... }
}

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

但是,从性能角度来看,最好在构造函数中进行绑定。 bind in constructor 方法将创建函数的单个实例并重新使用它,即使多次调用 render 方法也是如此。

所以你的第一种方法

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

在此您可以将 ThirdClass 中可用的任何参数传递给 prevItem 函数。 arguments.Like this

是调用父函数的好方法
<ThirdClass type="prev" onClick={()=>this.props.prevItem(firstArgument, secondArgument)} />

你的第二种方法是

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

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

Both the apporaches are right, it just that , it depends on your use case. Both of the approach using es6 arrow function and are right in above mentioned respective scenarios

I understand that arrow functions make things more efficient by not recreating the functions each time they are referred to

This is not true

箭头函数以词法方式处理 this 上下文,其中 "normal" 函数处理 dynamically. I wrote about the this key word in depth 如果您需要更多信息。

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

onClick={() => {}}

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

onClick={this.myHandler}


至于箭头函数作为 class 字段的好处(有一个 小的缺点 ,我会 post 它在答案的底部),如果你有一个正常的函数处理程序,需要通过 this:

访问 class 的当前实例
myHandler(){
  //  this.setState(...)
}

您需要明确 bind 它到 class
最常见的方法是在 constructor 中进行,因为它只运行一次:

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

如果你使用箭头函数作为处理程序,你不需要 bind 它到 class 因为如上所述,箭头函数使用 [= 的词法上下文23=]:

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

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

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

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

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

是如果你想把参数传递给本机旁边的处理程序event get passed,意思是你想向上传递一个参数。

如前所述,这将在每个渲染器上创建一个新的函数实例。
(对此有更好的方法,请继续阅读)。

运行 这种用例的示例:

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>

更好的方法是创建组件组合。
您可以创建一个包装相关标记的子组件,将拥有自己的处理程序,并将 datahandler 作为来自父组件的道具。

子组件然后将调用它从父组件获得的处理程序并将 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 字段下行:
正如我提到的,class 字段有一个小缺点。
class 方法和 class 字段的区别在于 class 字段附加到 class(构造函数)的 instance
其中 class 方法和对象附加到原型。

因此,如果此 class 的实例数量多得离谱,您 可能 会受到性能影响。

给定此代码块:

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;
}();

I understand that arrow functions make things more efficient by not recreating the functions each render similar to how binding in the constructor works.

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

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

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

但是请考虑以下情况

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

在上面的例子中,箭头函数并不是每次都重新创建函数,而是在实例化 class 时将上下文作为 An arrow function does not have its own this; the this value of the enclosing execution context is used. 绑定到 React 组件一次。这类似于 binding works is constructor。这是 proposed class fields for arrow functions 的一部分,不是 ES6 功能,

要理解您想问的问题,您必须知道函数从调用它的地方获取上下文。检查 以获得更多理解。

在您的例子中,您使用了 Arrow function 来定义 prevItem,因此它获得了封闭的 React 组件的上下文。

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

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

现在在其 child 中,即使您使用任何自定义上下文调用 prevItemusing bind or arrow functionprevItem 在 parent 中执行时,即 Main.js 将获取其封闭的 React 组件的上下文。并且由于您只想执行 prevItem 函数而不想将任何数据从 child 传递给它,因此写

<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>

因为它已经绑定在 parent.

现在即使你必须从 ThirdClass 和 SecondClass 向这些函数传递一些额外的数据,你也不应该直接使用 Arrow functionbind in render。在

上查看此答案

使用JavaScript curring function declaration,可以是其他答案的不同方式,注意以下代码:

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

现在JSX,你可以写:

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

clickHandlersomeData return 一个带有 e 参数的函数,但它不在 clickHandler 函数内部使用。所以效果很好。

为了写的更完整,可以这样写:

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

不需要e,为什么要写