为什么JSX道具不应该使用箭头函数或绑定?

时间:2016-04-17 14:22:20

标签: javascript reactjs ecmascript-6 jsx arrow-functions

我正在使用我的React应用程序运行lint,我收到此错误:

error    JSX props should not use arrow functions        react/jsx-no-bind

这就是我运行箭头功能的地方(在onClick内):

{this.state.photos.map(tile => (
  <span key={tile.img}>
    <Checkbox
      defaultChecked={tile.checked}
      onCheck={() => this.selectPicture(tile)}
      style={{position: 'absolute', zIndex: 99, padding: 5, backgroundColor: 'rgba(255, 255, 255, 0.72)'}}
    />
    <GridTile
      title={tile.title}
      subtitle={<span>by <b>{tile.author}</b></span>}
      actionIcon={<IconButton onClick={() => this.handleDelete(tile)}><Delete color="white"/></IconButton>}
    >
      <img onClick={() => this.handleOpen(tile.img)} src={tile.img} style={{cursor: 'pointer'}}/>
    </GridTile>
  </span>
))}

这是一种应该避免的不良做法吗?什么是最好的方法呢?

6 个答案:

答案 0 :(得分:138)

为什么不应在JSX道具中使用内联箭头功能

在JSX中使用箭头函数或绑定是一种伤害性能的不良做法,因为在每次渲染时都会重新创建该函数。

  1. 每当创建一个函数时,前一个函数都被垃圾收集。重新渲染许多元素可能会在动画中产生抖动。

  2. 使用内联箭头功能会导致PureComponentshallowCompare方法中使用shouldComponentUpdate的组件无论如何都会重新呈现。由于每次都会重新创建箭头函数道具,因此浅层比较会将其识别为道具的更改,并且该组件将会重新渲染。

  3. 正如您在以下2个示例中所看到的 - 当我们使用内联箭头函数时,每次都会重新呈现<Button>组件(控制台显示“渲染按钮”文本)。

    示例1 - PureComponent 不带内联处理程序

    class Button extends React.PureComponent {
      render() {
        const { onClick } = this.props;
        
        console.log('render button');
        
        return (
          <button onClick={ onClick }>Click</button>
        );
      }
    }
    
    class Parent extends React.Component {
      state = {
        counter: 0
      }
      
      onClick = () => this.setState((prevState) => ({
        counter: prevState.counter + 1
      }));
      
      render() {
        const { counter } = this.state;
        
        return (
          <div>
            <Button onClick={ this.onClick } />
            <div>{ counter }</div>
          </div>
        );
      }
    }
    
    ReactDOM.render(
      <Parent />,
      document.getElementById('root')
    );
    <script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
    <div id="root"></div>

    示例2 - PureComponent 内联处理程序

    class Button extends React.PureComponent {
      render() {
        const { onClick } = this.props;
        
        console.log('render button');
        
        return (
          <button onClick={ onClick }>Click</button>
        );
      }
    }
    
    class Parent extends React.Component {
      state = {
        counter: 0
      }
      
      render() {
        const { counter } = this.state;
        
        return (
          <div>
            <Button onClick={ () => this.setState((prevState) => ({
              counter: prevState.counter + 1
            })) } />
            <div>{ counter }</div>
          </div>
        );
      }
    }
    
    ReactDOM.render(
      <Parent />,
      document.getElementById('root')
    );
    <script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
    <div id="root"></div>

    在没有内联箭头功能的情况下将方法绑定到this

    1. 在构造函数中手动绑定方法:

      class Button extends React.Component {
        constructor(props, context) {
          super(props, context);
      
          this.cb = this.cb.bind(this);
        }
      
        cb() {
      
        }
      
        render() {
          return (
            <button onClick={ this.cb }>Click</button>
          );
        }
      }
      
    2. 使用带有箭头功能的proposal-class-fields绑定方法。由于这是第3阶段提案,因此您需要将Stage 3 presetClass properties transform添加到您的babel配置中。

      class Button extends React.Component {
        cb = () => { // the class property is initialized with an arrow function that binds this to the class
      
        }
      
        render() {
          return (
            <button onClick={ this.cb }>Click</button>
          );
        }
      }
      

答案 1 :(得分:8)

这是因为如果在JSX属性中使用,箭头函数显然会在每个渲染上创建一个新的函数实例。这可能会对垃圾收集器造成巨大压力,也会阻碍浏览器优化任何热路径#34;因为函数将被丢弃而不是重用。

您可以在https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md

查看完整说明和更多信息

答案 2 :(得分:4)

为了避免使用相同的参数创建新函数,您可以记住函数绑定结果,这是一个名为memobind的简单实用程序:https://github.com/supnate/memobind

答案 3 :(得分:0)

使用这样的内联函数非常好。起绒规则已过时。

此规则是从箭头功能不那么普遍并且人们使用.bind(this)的时候开始的。性能问题已在Chrome 49中修复。

请注意不要将内联函数作为道具传递给子组件。

React Router的作者Ryan Florence对此写了一篇很棒的文章:

https://cdb.reacttraining.com/react-inline-functions-and-performance-bdff784f5578

答案 4 :(得分:0)

您可以通过react-cached-handler库使用箭头功能,无需担心重新渲染性能:

  

注意:内部会按指定键缓存箭头功能,   无需担心重新渲染!

render() {

  return <div>
  {
        this.props.photos.map(photo=>
          <Photo key={photo.url}
            onClick={this.handler(photo.url, (url) => { 
                 console.log(url) })}
          />)
   }
 </div>

}

其他功能

  • 命名处理程序
  • 通过箭头函数处理事件
  • 访问键,自定义参数和原始事件
  • 组件渲染性能
  • 处理程序的自定义上下文

答案 5 :(得分:0)

为什么JSX道具不应该使用箭头功能或绑定?

主要是因为内联函数可能破坏优化组件的记忆:

传统上,React中与内联函数有关的性能问题与在每个渲染上传递新的回调如何中断子组件的shouldComponentUpdate优化有关。 (docs

与创建附加功能有关的费用较少:

Function.prototype.bind got fixed here的性能问题和箭头函数不是本机事物,就是由babel转换为普通函数;在这两种情况下,我们都可以假设它并不慢。 (React Training

我相信人们一直声称创建函数的成本过高,总是被误导(反应小组从未这样说)。 (Tweet

react/jsx-no-bind规则什么时候有用?

您要确保已记忆的组件按预期工作:

  • React.memo(用于功能组件)
  • PureComponent或自定义shouldComponentUpdate(用于类组件)

通过遵守此规则,可以传递稳定的函数对象引用。因此,在以前的道具没有更改的情况下,上述组件可以通过防止重新渲染来优化性能。

如何解决ESLint错误?

类:将处理程序定义为方法,或者将class property定义为this绑定。
挂钩:使用useCallback

Middleground

在许多情况下,内联函数非常方便使用,并且在性能要求方面绝对出色。不幸的是,该规则不能仅限于已记忆的组件类型。如果您仍然想全面使用它,可以例如disable it用于简单的DOM节点:

rules: {
  "react/jsx-no-bind": [ "error", { ignoreDOMComponents: true } ],
}

const Comp = () => <span onClick={() => console.log("Hello!")} />; // no warning