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


103

我正在用我的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>
))}

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

Answers:


170

为什么您不应该在JSX props中使用内联箭头功能

在JSX中使用箭头函数或绑定是一种不好的做法,因为它会在每个渲染器上重新创建,因此会损害性能。

  1. 每当创建一个函数时,就会对前一个函数进行垃圾回收。渲染许多元素可能会在动画中产生垃圾。

  2. 使用内联箭头功能将导致PureComponents和shallowCompareshouldComponentUpdate方法中使用的组件重新渲染。由于箭头函数prop每次都会重新创建,因此浅表比较会将其标识为对prop的更改,并且组件将重新呈现。

在下面的两个示例中可以看到-当我们使用嵌入式箭头功能时,<Button>每次都会重新渲染组件(控制台显示“渲染按钮”文本)。

示例1- 没有内联处理程序的PureComponent

示例2- 具有内联处理程序的PureComponent

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阶段的建议,因此您需要添加第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>
        );
      }
    }

具有内部回调的功能组件

当我们在函数组件内部创建内部函数(例如事件处理程序)时,每次渲染组件时都会重新创建该函数。如果将函数作为道具(或通过上下文)传递给子组件(Button在这种情况下),则该子组件也会重新渲染。

示例1-具有内部回调的功能组件:

为了解决这个问题,我们可以使用useCallback()hook来包装回调,并将依赖项设置为一个空数组。

注意:useState生成函数接受更新器功能,其提供当前状态。这样,我们无需将当前状态设置为的依赖项useCallback

示例2-具有内部回调的函数组件,其中内部回调包装有useCallback:


3
您如何在无状态组件上实现这一目标?
lux

4
无状态(功能)组件没有this,因此没有任何绑定。通常,方法由包装器智能组件提供。
Ori Drori '16

39
@OriDrori:当您需要在回调中传递数据时,该如何工作?onClick={() => { onTodoClick(todo.id) }
亚当·贝克

4
@ adam-beck-将其添加到类的回调方法定义中cb() { onTodoClick(this.props.todo.id); }
Ori Drori '16

2
@ adam-beck我认为这是如何useCallback与动态值一起使用的。stackoverflow.com/questions/55006061/...
翔太田村

9

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

您可以在https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md中查看整个说明和更多信息。


不仅。每次创建新的函数实例都意味着状态被修改,并且当组件的状态被修改时,它将被重新呈现。由于使用React的主要原因之一是仅渲染变化的元素,因此bind此处使用或箭头功能会让您大吃一惊。但是,它并没有得到很好的记录,尤其是在使用mapList等中的ping数组的情况下
。– hippietrail

“每次创建新功能实例都意味着状态被修改”,这是什么意思?根本没有问题
可言


4

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

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

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

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

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


您能否说明如何使用嵌入式箭头功能在组件上编写单元测试?
krankuba

1
@krankuba这不是这个问题。您仍然可以传递未内联定义但仍不可测试的匿名函数。
sbaechler '19

-1

您可以使用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>

}

其他特性:

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

问题是为什么我们不能使用它。不是如何与其他黑客一起使用它。
卡皮尔

-1

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

通常,由于内联函数可能破坏优化组件的记忆:

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

它与额外的函数创建成本无关:

Function.prototype.bind 此处的性能问题已得到解决,箭头函数要么是本地问题,要么由babel转换为简单函数;在这两种情况下,我们都可以假设它并不慢。(React培训

我相信人们一直声称创建函数昂贵,总是被误解(React团队从未这样说)。(鸣叫

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

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

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

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

如何解决ESLint错误?

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

中间地带

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

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

const Comp = () => <span onClick={() => console.log("Hello!")} />; // no warning
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.