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

Kad*_*BOT 92 javascript jsx ecmascript-6 reactjs arrow-functions

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

error    JSX props should not use arrow functions        react/jsx-no-bind
Run Code Online (Sandbox Code Playgroud)

这就是我正在运行箭头功能(内部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>
))}
Run Code Online (Sandbox Code Playgroud)

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

Ori*_*ori 151

为什么你不应该在JSX道具中使用内联箭头函数

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

  1. 无论何时创建函数,前一个函数都是垃圾收集的.重新渲染许多元素可能会在动画中创建jank.

  2. 使用在线箭头功能会引起PureComponents,而使用的组件shallowCompareshouldComponentUpdate方法无论如何重新呈现.由于每次都会重新创建箭头函数prop,因此浅层比较会将其识别为对prop的更改,并且该组件将重新渲染.

正如您在以下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')
);
Run Code Online (Sandbox Code Playgroud)
<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>
Run Code Online (Sandbox Code Playgroud)

示例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')
);
Run Code Online (Sandbox Code Playgroud)
<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>
Run Code Online (Sandbox Code Playgroud)

绑定方法到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>
        );
      }
    }
    
    Run Code Online (Sandbox Code Playgroud)
  2. 使用投标类字段和箭头函数绑定方法.由于这是第3阶段提案,因此您需要将Stage 3预设Class属性转换添加到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>
        );
      }
    }
    
    Run Code Online (Sandbox Code Playgroud)

  • @OriDrori:当你需要在回调中传递数据时,它是如何工作的?`onClick = {()=> {onTodoClick(todo.id)}` (37认同)
  • 无状态(函数)组件没有`this`,所以没有什么可绑定的.通常,这些方法由包装器智能组件提供. (4认同)
  • 你如何在无状态组件上实现这一目标? (3认同)
  • @ adam-beck - 将它添加到类`cb(){onTodoClick(this.props.todo.id);的回调方法定义中; }`. (3认同)
  • @adam-beck我认为这就是如何使用具有动态值的`useCallback`。/sf/ask/3850424301/ (2认同)

Kar*_*ren 8

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

你可以在https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md看到整个解释和更多信息.


sba*_*ler 6

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

这个规则是在箭头函数不那么常见并且人们使用 .bind(this) 的时候开始的,这曾经很慢。性能问题已在 Chrome 49 中修复。

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

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

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


for*_*d04 5

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

大多数情况下,因为内联函数会破坏优化组件的记忆:

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

它不是额外的函数创建成本:

性能问题Function.prototype.bind 在这里得到修复,箭头函数要么是原生的,要么被 babel 转译为普通函数;在这两种情况下,我们都可以假设它并不慢。(反应训练

我相信声称函数创建成本高昂的人总是被误导(React 团队从未说过这一点)。(推文

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

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

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

通过遵守此规则,传递稳定的函数对象引用。所以当之前的 props 没有改变时,上面的组件可以通过防止重新渲染来优化性能。

如何解决 ESLint 错误?

类:将处理程序定义为方法或用于绑定的类属性this
挂钩:使用useCallback.

中间地带

在很多情况下,内联函数使用起来非常方便,而且在性能要求方面绝对没问题。不幸的是,这条规则不能仅限于记忆化的组件类型。如果您仍然想全面使用它,您可以例如为简单的 DOM 节点禁用它

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

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


fru*_*oaf 5

您可以通过将函数包装在 useCallback 中来消除此错误。