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

IT技术 javascript reactjs ecmascript-6 jsx arrow-functions
2021-01-10 12:05:41

我正在使用 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个回答

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

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

  1. 每当创建一个函数时,前一个函数都会被垃圾回收。重新渲染许多元素可能会导致动画卡顿。

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

正如您在以下 2 个示例中所看到的 - 当我们使用内联箭头函数时,<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 阶段预设类属性转换添加到您的 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>
        );
      }
    }
    

具有内部回调的函数组件

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

示例 1 - 带有内部回调的函数组件:

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

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

示例 2 - 内部回调函数组件用 useCallback 包裹:

@adam-beck 我认为这是如何使用useCallback动态值。stackoverflow.com/questions/55006061/...
2021-03-11 12:05:41
@adam-beck - 将其添加到类的回调方法定义中cb() { onTodoClick(this.props.todo.id); }
2021-03-24 12:05:41
你如何在无状态组件上实现这一点?
2021-03-26 12:05:41
@OriDrori:当您需要在回调中传递数据时,它是如何工作的? onClick={() => { onTodoClick(todo.id) }
2021-03-30 12:05:41
无状态(函数)组件没有this,所以没有什么可以绑定的。通常这些方法由包装器智能组件提供。
2021-03-31 12:05:41

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

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

“每次创建新的函数实例都意味着状态被修改”你是什么意思?问题中根本没有状态
2021-03-20 12:05:41
不仅。每次创建新的函数实例意味着状态被修改,当组件的状态被修改时,它将被重新渲染。由于使用 React 的主要原因之一是只渲染发生变化的元素,因此bind在这里使用或 箭头函数是在射击自己。但是,没有很好的文档记录,特别是在使用map列表中的 ping 数组等的情况下
2021-03-28 12:05:41

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

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

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

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

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

您能否展示如何使用内联箭头函数对组件编写单元测试?ˆˆ
2021-03-27 12:05:41
@krankuba 这不是这个问题的内容。您仍然可以传入未内联定义但仍不可测试的匿名函数。
2021-03-29 12:05:41

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

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

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

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

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

我相信声称函数创建成本高昂的人总是被误导(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

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