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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/36677733/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 19:25:30  来源:igfitidea点击:

Why shouldn't JSX props use arrow functions or bind?

javascriptreactjsecmascript-6jsxarrow-functions

提问by KadoBOT

I'm running lint with my React app, and I receive this error:

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

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

And this is where I'm running the arrow function (inside onClick):

这就是我运行箭头函数的地方(内部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>
))}

Is this a bad practice that should be avoided? And what's the best way to do it?

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

回答by Ori Drori

Why you shouldn't use inline arrow functions in JSX props

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

Using arrow functions or binding in JSX is a bad practice that hurts performance, because the function is recreated on each render.

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

  1. Whenever a function is created, the previous function is garbage collected. Rerendering many elements might create jank in animations.

  2. Using an inline arrow function will cause PureComponents, and components that use shallowComparein the shouldComponentUpdatemethod to rerender anyway. Since the arrow function prop is recreated each time, the shallow compare will identify it as a change to a prop, and the component will rerender.

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

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

As you can see in the following 2 examples - when we use inline arrow function, the <Button>component is rerendered each time (the console shows the 'render button' text).

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

Example 1 - PureComponent withoutinline handler

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

Example 2 - PureComponent withinline handler

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

Binding methods to thiswithout inlining arrow functions

将方法绑定到this不内联箭头函数

  1. Binding the method manually in the constructor:

    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. Binding a method using the proposal-class-fieldswith an arrow function. As this is a stage 3 proposal, you'll need to add the Stage 3 presetor the Class properties transformto your babel configuration.

    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. 在构造函数中手动绑定方法:

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

Function Components with inner callbacks

具有内部回调的函数组件

When we create an inner function (event handler for example) inside a function component, the function will be recreated every time the component is rendered. If the function is passed as props (or via context) to a child component (Buttonin this case), that child will re-render as well.

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

Example 1 - Function Component with an inner callback:

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

const { memo, useState } = React;

const Button = memo(({ onClick }) => console.log('render button') || (
  <button onClick={onClick}>Click</button>
));

const Parent = () => {
  const [counter, setCounter] = useState(0);
  
  const increment = () => setCounter(counter => counter + 1); // the function is recreated all the time
  
  return (
    <div>
      <Button onClick={increment} />
      
      <div>{counter}</div>
    </div>
  );
}

ReactDOM.render(
  <Parent />,
  document.getElementById('root')
);
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

<div id="root"></div>

To solve this problem, we can wrap the callback with the useCallback()hook, and set the dependencies to an empty array.

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

Note:the useStategenerated function accepts an updater function, that provides the current state. In this way, we don't need to set the current state a dependency of useCallback.

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

Example 2 - Function Component with an inner callback wrapped with useCallback:

示例 2 - 带有使用 useCallback 包装的内部回调的函数组件:

const { memo, useState, useCallback } = React;

const Button = memo(({ onClick }) => console.log('render button') || (
  <button onClick={onClick}>Click</button>
));

const Parent = () => {
  const [counter, setCounter] = useState(0);
  
  const increment = useCallback(() => setCounter(counter => counter + 1), []);
  
  return (
    <div>
      <Button onClick={increment} />
      
      <div>{counter}</div>
    </div>
  );
}

ReactDOM.render(
  <Parent />,
  document.getElementById('root')
);
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

<div id="root"></div>

回答by Karl-Johan Sj?gren

This is because an arrow function apparently will create a new instance of the function on each render if used in a JSX property. This might create a huge strain on the garbage collector and will also hinder the browser from optimizing any "hot paths" since functions will be thrown away instead of reused.

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

You can see the whole explanation and some more info at https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md

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

回答by supNate

To avoid creating new functions with the same arguments, you could memoize the function bind result, here is a simple utility named memobindto do it: https://github.com/supnate/memobind

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

回答by sbaechler

Using inline functions like this is perfectly fine. The linting rule is outdated.

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

This rule is from a time when arrow functions were not as common and people used .bind(this), which used to be slow. The performance issue has been fixed in Chrome 49.

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

Do pay attention that you do not pass inline functions as props to a child component.

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

Ryan Florence, the author of React Router, has written a great piece about this:

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

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

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

回答by Ghominejad

You can use arrow functions using react-cached-handlerlibrary, no need to be worried about re-rendering performance :

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

Note : Internally it caches your arrow functions by the specified key, no need to be worried about re-rendering!

注意:它在内部通过指定的键缓存您的箭头函数,无需担心重新渲染!

render() {

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

}

Other features:

其他特性:

  • Named handlers
  • Handle events by arrow functions
  • Access to the key, custom arguments and the original event
  • Component rendering performace
  • Custom context for handlers
  • 命名处理程序
  • 通过箭头函数处理事件
  • 访问键、自定义参数和原始事件
  • 组件渲染性能
  • 处理程序的自定义上下文

回答by ford04

Why shouldn't JSX props use arrow functions or bind?

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

Mostly, because inline functions can break memoization of optimized components:

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

Traditionally, performance concerns around inline functions in React have been related to how passing new callbacks on each render breaks shouldComponentUpdateoptimizations in child components. (docs)

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

It is less about additional function creation cost:

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

Performance issues with Function.prototype.bindgot fixed hereand arrow functions are either a native thing or are transpiled by babel to plain functions; in both cases we can assume it's not slow. (React Training)

I believe people claiming function creation is expensive have always been misinformed (React team never said this). (Tweet)

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

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

When is the react/jsx-no-bindrule useful?

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

You want to ensure, that memoized components work as intended:

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

  • React.memo(for function components)
  • PureComponentor custom shouldComponentUpdate(for class components)
  • React.memo(对于功能组件)
  • PureComponent或自定义shouldComponentUpdate(用于类组件)

By obeying to this rule, stable function object references are passed. So above components can optimize performance by preventing re-renders, when previous props have not changed.

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

How to solve the ESLint error?

如何解决 ESLint 错误?

Classes: Define the handler as method, or class propertyfor thisbinding.
Hooks: Use useCallback.

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

Middleground

中间地带

In many cases, inline functions are very convenient to use and absolutely fine in terms of performance requirements. Unfortunately, this rule cannot be limited to only memoized component types. If you still want to use it across-the-board, you could e.g. disable itfor simple DOM nodes:

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

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

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