为什么 JSX 道具不应该使用箭头函数或绑定? [英] Why shouldn't JSX props use arrow functions or bind?

查看:29
本文介绍了为什么 JSX 道具不应该使用箭头函数或绑定?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

error JSX props 不应该使用箭头函数 react/jsx-no-bind

这就是我运行箭头函数的地方(在 onClick 中):

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

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

解决方案

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

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

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

  2. 使用内联箭头函数将导致 PureComponent 和在 shouldComponentUpdate 方法中使用 shallowCompare 的组件无论如何都要重新渲染.由于每次都会重新创建箭头函数 prop,因此浅比较会将其识别为对 prop 的更改,并且组件将重新渲染.

正如您在以下 2 个示例中所见 - 当我们使用内联箭头函数时,

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

class Button extends React.PureComponent {使成为() {const { onClick } = this.props;console.log('渲染按钮');返回 (<button onClick={ onClick }>点击</button>);}}class Parent 扩展 React.Component {状态 = {计数器:0}使成为() {const { 计数器 } = this.state;返回 (<div><按钮onClick={()=>this.setState((prevState) => ({计数器:prevState.counter + 1})) }/><div>{计数器}</div>

);}}ReactDOM.render(<父/>,document.getElementById('root'));

<script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></脚本><script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script><div id="root"></div>

将方法绑定到this,无需内联箭头函数

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

    class Button 扩展 React.Component {构造函数(道具,上下文){超级(道具,上下文);this.cb = this.cb.bind(this);}CB(){}使成为() {返回 (<button onClick={ this.cb }>点击</button>);}}

  2. 使用带有箭头的 proposal-class-fields 绑定方法功能.由于这是第 3 阶段的提案,您需要添加 第 3 阶段预设类属性转换到您的 babel 配置.

    class Button 扩展 React.Component {cb = () =>{//类属性用一个箭头函数初始化,该函数将 this 绑定到类}使成为() {返回 (<button onClick={ this.cb }>点击</button>);}}

具有内部回调的函数组件

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

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

const { memo, useState } = React;const Button = memo(({ onClick }) => console.log('渲染按钮') || (<button onClick={onClick}>点击</button>));const Parent = () =>{const [计数器,setCounter] = useState(0);常量增量 = () =>setCounter(counter => counter + 1);//函数一直在重新创建返回 (<div><Button onClick={增量}/><div>{计数器}</div>

);}ReactDOM.render(<父/>,document.getElementById('root'));

<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script><div id="root"></div>

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

注意: useState 生成的函数接受更新程序函数,该函数提供当前状态.这样,我们就不需要为当前状态设置useCallback的依赖.

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

const { memo, useState, useCallback } = React;const Button = memo(({ onClick }) => console.log('渲染按钮') || (<button onClick={onClick}>点击</button>));const Parent = () =>{const [计数器,setCounter] = useState(0);const increment = useCallback(() => setCounter(counter => counter + 1), []);返回 (<div><Button onClick={增量}/><div>{计数器}</div>

);}ReactDOM.render(<父/>,document.getElementById('root'));

<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script><div id="root"></div>

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

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

And this is where I'm running the arrow function (inside 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?

解决方案

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

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

  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 shallowCompare in the shouldComponentUpdate method 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.

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).

Example 1 - PureComponent without inline handler

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 with inline handler

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 this without inlining arrow functions

  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-fields with an arrow function. As this is a stage 3 proposal, you'll need to add the Stage 3 preset or the Class properties transform to 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>
        );
      }
    }
    

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 (Button in this case), that child will re-render as well.

Example 1 - Function Component with an inner callback:

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.

Note: the useState generated 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.

Example 2 - Function Component with an inner callback wrapped with 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>

这篇关于为什么 JSX 道具不应该使用箭头函数或绑定?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
前端开发最新文章
热门教程
热门工具
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆