1. ホーム
  2. javascript

[解決済み] なぜJSXプロップは矢印関数やバインドを使用してはいけないのですか?

2022-07-03 04:56:54

質問

Reactアプリでlintを実行しているのですが、こんなエラーが出ます。

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

そして、ここでarrow関数を実行しています(内部は 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>
))}

これは避けるべき悪い習慣なのでしょうか?また、どのような方法が良いのでしょうか?

どのように解決するのですか?

JSXプロップスでインライン矢印関数を使用してはいけない理由

JSXで矢印関数やバインディングを使用することは、レンダリングのたびに関数が再作成されるため、パフォーマンスに影響を与える悪い習慣です。

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

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

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

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

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

へのバインディングメソッド 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. メソッドをバインドするには 提案クラス-フィールド を矢印の関数で結合します。これはステージ3の提案であるため、ステージ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() フック で、依存関係を空の配列に設定します。

注意してください。 その 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>