web-dev-qa-db-ja.com

JSXプロップが矢印関数を使用したりバインドしたりしないのはなぜですか

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

これは避けるべき悪い習慣ですか?そして、それを行う最良の方法は何ですか?

87
KadoBOT

JSX propsでインライン矢印関数を使用しない理由

JSXで矢印関数またはバインドを使用すると、レンダリングのたびに関数が再作成されるため、パフォーマンスを損なう悪い習慣です。

  1. 関数が作成されるたびに、前の関数はガベージコレクションされます。多くの要素をレンダリングすると、アニメーションに迷惑がかかる可能性があります。

  2. インライン矢印関数を使用すると、PureComponentsと、shallowCompareメソッドでshouldComponentUpdateを使用するコンポーネントがとにかく再レンダリングされます。矢印関数propは毎回再作成されるため、浅い比較はそれをpropへの変更として識別し、コンポーネントは再レンダリングされます。

次の2つの例でわかるように、インライン矢印関数を使用すると、<Button>コンポーネントが毎回再レンダリングされます(コンソールに「レンダリングボタン」テキストが表示されます)。

例1-PureComponentwithoutインラインハンドラー

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>

例2-PureComponentwithインラインハンドラー

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>

矢印関数をインライン化せずに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>
        );
      }
    }
    

内部コールバックを持つ関数コンポーネント

関数コンポーネント内に内部関数(たとえば、イベントハンドラー)を作成すると、コンポーネントがレンダリングされるたびに関数が再作成されます。関数が小道具として(またはコンテキストを介して)子コンポーネント(この場合はButton)に渡されると、その子も再レンダリングされます。

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

この問題を解決するには、コールバックを useCallback() hook でラップし、依存関係を空の配列に設定します。

注:useState生成関数は、現在の状態を提供するアップデーター関数を受け入れます。このように、現在の状態を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>
141
Ori Drori

これは、JSXプロパティで使用すると、矢印関数が各レンダリングで関数の新しいインスタンスを作成するためです。これは、ガベージコレクターに大きな負担をかける可能性があり、また、関数が再利用される代わりに破棄されるため、ブラウザーが「ホットパス」を最適化することを妨げる可能性があります。

説明全体と詳細情報を https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md で見ることができます。

8

同じ引数を持つ新しい関数の作成を避けるために、関数バインドの結果をメモすることができます。これを行うmemobindという名前の簡単なユーティリティがあります。 https://github.com/supnate/memobind

4
supNate

このようなインライン関数を使用しても問題ありません。リンティング規則は時代遅れです。

このルールは、矢印関数がそれほど一般的ではなく、かつては低速だった.bind(this)を使用していた時代のものです。パフォーマンスの問題はChrome 49で修正されました。

インライン関数を子コンポーネントの小道具として渡さないことに注意してください。

React Routerの作者であるRyan Florenceは、これについて素晴らしい記事を書いています。

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

2
sbaechler

react-cached-handler libraryを使用して矢印関数を使用できます。再レンダリングのパフォーマンスを心配する必要はありません。

注:内部では、指定されたキーによって矢印関数をキャッシュします。再レンダリングについて心配する必要はありません!

render() {

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

}

その他の機能:

  • 名前付きハンドラー
  • 矢印関数によるイベントの処理
  • キー、カスタム引数、元のイベントへのアクセス
  • コンポーネントレンダリングパフォーマンス
  • ハンドラーのカスタムコンテキスト
0
Ghominejad