ReactクラスコンポーネントをReactフックを使用して機能コンポーネントに変換する方法

提供:Dev Guides
移動先:案内検索

序章

Reactの最新のアルファリリースでは、フックと呼ばれる新しいコンセプトが導入されました。 一般的な問題を解決するためにReactにフックが導入されました。 ただし、これらは主にクラスの代替として機能します。 フックを使用すると、状態メソッドとライフサイクルメソッドを使用する機能コンポーネントを作成できます。

フックは現在、Reactv16.7.0-alphaで利用できます。 クラスを削除する予定はありません。 フックはReactを書く別の方法を提供します。

フックがまだ新しいことを考えると、多くの開発者は、既存のReactアプリケーションまたは新しいアプリケーションにこの概念を適用しようとしています。 この投稿では、Reactフックを使用してReactクラスコンポーネントを機能コンポーネントに変換する5つの方法について説明します。

前提条件

このチュートリアルを完了するには、次のものが必要です。

  • JavaScriptに精通していること。 JavaScriptでコーディングする方法シリーズを確認して、詳細を確認し、開始することができます。
  • Reactに精通している。 始めるのに役立つガイドについては、React.jsシリーズのコーディング方法を確認してください。

ローカル開発は必要ありませんが、CodeSandboxの例がさらなる実験のために提供されています。

ステップ1—状態またはライフサイクルメソッドのないクラスを理解する

状態コンポーネントもライフサイクルコンポーネントもないReactクラスから始めましょう。

ExampleClassComponent.js

import React, { Component } from 'react';

class App extends Component {
  alertName = () => {
    alert('John Doe');
  };

  render() {
    return (
      <div>
        <h3>This is a Class Component</h3>
        <button onClick={this.alertName}>
          Alert
        </button>
      </div>
    );
  }
};

export default App;

ここに、状態またはライフサイクルメソッドがない典型的なReactクラスがあります。 ボタンがクリックされると名前を警告します。

このクラスと同等の機能は次のようになります。

ExampleFunctionalComponent.js

import React from 'react';

function App() {
  const alertName = () => {
    alert('John Doe');
  };

  return (
    <div>
      <h3>This is a Functional Component</h3>
      <button onClick={alertName}>
        Alert
      </button>
    </div>
  );
};

export default App;

最初の例のように、この関数クラスは通常の方法で動作します。

ただし、この例では、フックなどの新しいものはまだ使用していません。 これらの例では、状態やライフサイクルは必要ありません。

状態を持つクラスベースのコンポーネントを見て、フックを使用してそれらを機能コンポーネントに変換する方法を学びましょう。

ステップ2—状態のあるクラスにフックを追加する

テキスト入力フィールドからアプリ内で更新できるグローバル名変数がある状況を考えてみましょう。

Reactでは、stateオブジェクトでname変数を定義し、name変数を更新する新しい値があるときにsetState()を呼び出すことで、このようなケースを処理します。

ExampleClassComponentWithState.js

import React, { Component } from 'react';

class App extends Component {
  state = {
    name: ''
  }

  alertName = () => {
    alert(this.state.name);
  };

  handleNameInput = e => {
    this.setState({ name: e.target.value });
  };

  render() {
    return (
      <div>
        <h3>This is a Class Component</h3>
        <input
          type="text"
          onChange={this.handleNameInput}
          value={this.state.name}
          placeholder="Your Name"
        />
        <button onClick={this.alertName}>
          Alert
        </button>
      </div>
    );
  }
}

export default App;

ユーザーが入力フィールドに名前を入力してAlertボタンをクリックすると、stateで定義された名前のアラートがポップアップ表示されます。

フックを使用して、このクラス全体を機能的なReactコンポーネントに変換できます。

ExampleFunctionalComponentWithState.js

import React, { useState } from 'react';

function App() {
  const [name, setName] = useState('John Doe');

  const alertName = () => {
    alert(name);
  };

  const handleNameInput = e => {
    setName(e.target.value);
  };

  return (
    <div>
      <h3>This is a Functional Component</h3>
      <input
        type="text"
        onChange={handleNameInput}
        value={name}
        placeholder="Your Name"
      />
      <button onClick={alertName}>
        Alert
      </button>
    </div>
  );
};

export default App;

ここでは、useStateフックを紹介しました。 これにより、React機能コンポーネントの状態を利用できます。 useState()フックを使用すると、この機能コンポーネントで状態を使用できます。 これは、配列の破壊的な割り当てで同様の構文を使用します。

次の行を検討してください。

const [name, setName] = useState('John Doe')

ここで、nameは、通常のクラスコンポーネントのthis.stateと同等であり、setNameは、this.setStateと同等です。

useState()フックの状態の初期値は引数から取得されます。 つまり、useState()引数は状態の初期値です。 あなたの場合、それを'John Doe'に設定します。 これは、状態の名前の初期状態が'John Doe'であることを意味します。

このコードは、フックを使用して、状態のあるクラスベースのReactコンポーネントを機能コンポーネントに変換する方法の例です。

複数の状態プロパティを持つクラスを含む、他のシナリオを調べてみましょう。

ステップ3—複数の状態プロパティを持つクラスにフックを追加する

useStateを使用して1つの状態プロパティを変換する方法を見てきましたが、複数の状態プロパティがある場合、同じアプローチは完全には機能しません。 たとえば、userNamefirstName、およびlastNameに2つ以上の入力フィールドがある場合、次の3つの状態プロパティを持つクラスベースのコンポーネントがあります。

ExampleClassComponentWithMultipleStateProperties.js

import React, { Component } from 'react';

class App extends Component {
  state = {
    userName: '',
    firstName: '',
    lastName: ''
  };

  logName = () => {
    console.log(this.state.userName);
    console.log(this.state.firstName);
    console.log(this.state.lastName);
  };

  handleUserNameInput = e => {
    this.setState({ userName: e.target.value });
  };
  handleFirstNameInput = e => {
    this.setState({ firstName: e.target.value });
  };
  handleLastNameInput = e => {
    this.setState({ lastName: e.target.value });
  };

  render() {
    return (
      <div>
        <h3>This is a Class Component</h3>
        <input
          type="text"
          onChange={this.handleUserNameInput}
          value={this.state.userName}
          placeholder="Your Username"
        />
        <input
          type="text"
          onChange={this.handleFirstNameInput}
          value={this.state.firstName}
          placeholder="Your First Name"
        />
        <input
          type="text"
          onChange={this.handleLastNameInput}
          value={this.state.lastName}
          placeholder="Your Last Name"
        />
        <button
          className="btn btn-large right"
          onClick={this.logName}
        >
          Log Names
        </button>
      </div>
    );
  }
}

export default App;

このクラスをフックを使用して機能コンポーネントに変換するには、やや型破りな方法をとる必要があります。 useState()フックを使用すると、上記の例は次のように記述できます。

ExampleFunctionalComponentWithMultipleStateProperties.js

import React, { useState } from 'react';

function App() {
  const [userName, setUsername] = useState('');
  const [firstName, setFirstname] = useState('');
  const [lastName, setLastname] = useState('');

  const logName = () => {
    console.log(userName);
    console.log(firstName);
    console.log(lastName);
  };

  const handleUserNameInput = e => {
    setUsername(e.target.value);
  };
  const handleFirstNameInput = e => {
    setFirstname(e.target.value);
  };
  const handleLastNameInput = e => {
    setLastname(e.target.value);
  };

  return (
    <div>
      <h3>This is a Functional Component</h3>
      <input
        type="text"
        onChange={handleUserNameInput}
        value={userName}
        placeholder="Your Username"
      />
      <input
        type="text"
        onChange={handleFirstNameInput}
        value={firstName}
        placeholder="Your First Name"
      />
      <input
        type="text"
        onChange={handleLastNameInput}
        value={lastName}
        placeholder="Your Last Name"
      />
      <button
        className="btn btn-large right"
        onClick={logName}
      >
        Log Names
      </button>
    </div>
  );
};

export default App;

この例のCodeSandboxを次に示します。

これは、useState()フックを使用して、複数の状態プロパティを持つクラスベースのコンポーネントを機能コンポーネントに変換する方法を示しています。

ステップ4—状態とcomponentDidMountを使用してクラスにフックを追加する

statecomponentDidMountのクラスを考えてみましょう。 実例を示すために、3つの入力フィールドに初期状態を設定し、5秒後にすべてを異なる値のセットに更新するシナリオを見ていきます。

これを実現するには、入力フィールドの初期状態値を宣言し、初期レンダリング後に実行されて状態値を更新するcomponentDidMount()ライフサイクルメソッドを実装します。

ExampleClassComponentWithStateAndComponentDidMount.js

import React, { Component } from 'react';

class App extends Component {
  state = {
    // initial state
    userName: 'johndoe',
    firstName: 'John',
    lastName: 'Doe'
  }

  componentDidMount() {
    setInterval(() => {
      this.setState({
        // update state
        userName: 'janedoe',
        firstName: 'Jane',
        lastName: 'Doe'
      });
    }, 5000);
  }

  logName = () => {
    console.log(this.state.userName);
    console.log(this.state.firstName);
    console.log(this.state.lastName);
  };

  handleUserNameInput = e => {
    this.setState({ userName: e.target.value });
  };
  handleFirstNameInput = e => {
    this.setState({ firstName: e.target.value });
  };
  handleLastNameInput = e => {
    this.setState({ lastName: e.target.value });
  };

  render() {
    return (
      <div>
        <h3>This is a Class Component</h3>
        <input
          type="text"
          onChange={this.handleUserNameInput}
          value={this.state.userName}
          placeholder="Your Username"
        />
        <input
          type="text"
          onChange={this.handleFirstNameInput}
          value={this.state.firstName}
          placeholder="Your First Name"
        />
        <input
          type="text"
          onChange={this.handleLastNameInput}
          value={this.state.lastName}
          placeholder="Your Last Name"
        />
        <button
          className="btn btn-large right"
          onClick={this.logName}
        >
          Log Names
        </button>
      </div>
    );
  }
}

export default App;

アプリを実行すると、入力フィールドには、状態オブジェクトで定義した初期値が含まれます。 これらの値は、5秒後にcomponentDidMount()メソッド内で定義した値に更新されます。

次に、React useStateおよびuseEffectフックを使用して、このクラスを機能コンポーネントに変換します。

ExampleFunctionalComponentWithStateAndComponentDidMount.js

import React, { useState, useEffect } from 'react';

function App() {
  const [userName, setUsername] = useState('johndoe');
  const [firstName, setFirstname] = useState('John');
  const [lastName, setLastname] = useState('Doe');

  useEffect(() => {
    setInterval(() => {
      setUsername('janedoe');
      setFirstname('Jane');
      setLastname('Doe');
    }, 5000);
  });

  const logName = () => {
    console.log(userName);
    console.log(firstName);
    console.log(lastName);
  };

  const handleUserNameInput = e => {
    setUsername({ userName: e.target.value });
  };
  const handleFirstNameInput = e => {
    setFirstname({ firstName: e.target.value });
  };
  const handleLastNameInput = e => {
    setLastname({ lastName: e.target.value });
  };

  return (
    <div>
      <h3>This is a Functional Component</h3>
      <input
        type="text"
        onChange={handleUserNameInput}
        value={userName}
        placeholder="Your Username"
      />
      <input
        type="text"
        onChange={handleFirstNameInput}
        value={firstName}
        placeholder="Your First Name"
      />
      <input
        type="text"
        onChange={handleLastNameInput}
        value={lastName}
        placeholder="Your Last Name"
      />
      <button
        className="btn btn-large right"
        onClick={logName}
      >
        Log Names
      </button>
    </div>
  );
};

export default App;

この例のCodeSandboxを次に示します。

機能面では、このコンポーネントは前の例とまったく同じことを行います。 唯一の違いは、クラスコンポーネントで行ったように、従来のstateオブジェクトとcomponentDidMount()ライフサイクルメソッドを使用する代わりに、useStateuseEffectを使用したことです。フック。

ステップ5— State、componentDidMount、およびcomponentDidUpdateを使用してクラスにフックを追加する

次に、状態と2つのライフサイクルメソッドcomponentDidMountcomponentDidUpdateを持つReactクラスを見てみましょう。 これまでのソリューションのほとんどは、useStateフックを使用していました。 この例では、useEffectフックに注目します。

これがどのように機能するかを最もよく示すために、ページの<h3>ヘッダーを動的に更新するようにコードを変更してみましょう。

現在、ヘッダーにはThis is a Class Componentと表示されています。 次に、componentDidMount()メソッドを定義して、3秒後にWelcome to React Hooksとなるようにヘッダーを更新します。

ExampleClassComponentWithStateAndTwoLifecycleMethods.js

import React, { Component } from 'react';

class App extends Component {
  state = {
    header: 'Welcome to React Hooks'
  }

  componentDidMount() {
    const header = document.querySelectorAll('#header')[0];
    setTimeout(() => {
      header.innerHTML = this.state.header;
    }, 3000);
  }

  render() {
    return (
      <div>
        <h3 id="header">This is a Class Component</h3>
      </div>
    );
  }
}

export default App;

アプリが実行されると、最初のヘッダーThis is a Class Componentで起動し、3秒後にWelcome to React Hooksに変更されます。 これは、render関数が正常に実行された後に実行されるため、従来のcomponentDidMount()の動作です。

別の入力フィールドからヘッダーを動的に更新する機能を追加して、入力中にヘッダーが新しいテキストで更新されるようにします。

これを実現するには、componentDidUpdate()ライフサイクルメソッドを実装する必要があります。

ExampleClassComponent.js

import React, { Component } from 'react';

class App extends Component {
  state = {
    header: 'Welcome to React Hooks'
  }

  componentDidMount() {
    const header = document.querySelectorAll('#header')[0];
    setTimeout(() => {
      header.innerHTML = this.state.header;
    }, 3000);
  }

  componentDidUpdate() {
    const node = document.querySelectorAll('#header')[0];
    node.innerHTML = this.state.header;
  }

  handleHeaderInput = e => {
    this.setState({ header: e.target.value });
  };

  render() {
    return (
      <div>
        <h3 id="header">This is a Class Component</h3>
        <input
          type="text"
          onChange={this.handleHeaderInput}
          value={this.state.header}
        />
      </div>
    );
  }
}

export default App;

ここには、statecomponentDidMount()、およびcomponentDidUpdate()があります。 アプリを実行すると、componentDidMount()関数は、3秒後にヘッダーをWelcome to React Hooksに更新します。 ヘッダーテキスト入力フィールドに入力を開始すると、<h3>テキストは、componentDidUpdate()メソッドで定義された入力テキストで更新されます。

次に、このクラスをuseEffect()フックを使用して機能コンポーネントに変換します。

ExampleFunctionalComponentWithStateAndTwoLifecycleMethods.js

import React, { useState, useEffect } from 'react';

function App() {
  const [header, setHeader] = useState('Welcome to React Hooks');

  useEffect(() => {
    const newheader = document.querySelectorAll('#header')[0];
    setTimeout(() => {
      newheader.innerHTML = header;
    }, 3000);
  });

  const handleHeaderInput = e => {
    setHeader(e.target.value);
  };

  return (
    <div>
      <h3 id="header">This is a Functional Component</h3>
      <input
        type="text"
        onChange={handleHeaderInput}
        value={header}
      />
    </div>
  );
};

export default App;

CodeSandboxでこの例を確認してください。

このコンポーネントでは、useEffect()フックを使用して以前と同じ機能を実現しました。 componentDidMount()関数とcomponentDidUpdate()関数に別々のコードを記述する必要がなかったため、コードも最適化しました。 useEffect()フックを使用すると、両方の機能を利用できます。 これは、useEffect()がデフォルトで最初のレンダリング後と、その後のすべての更新後の両方で実行されるためです。

ステップ6—PureComponentReact memoに変換する

ReactPureComponentComponentと同じように機能します。 それらの主な違いは、React.ComponentshouldComponentUpdate()ライフサイクルメソッドを実装していませんが、React.PureComponentは実装していることです。

render()関数が同じ小道具と状態で同じ結果をレンダリングするアプリケーションがある場合、場合によってはReact.PureComponentを使用してパフォーマンスを向上させることができます。

React.memo()も同様に機能します。 関数コンポーネントが同じ小道具を指定して同じ結果をレンダリングする場合、React.memo()の呼び出しでそれをラップして、パフォーマンスを向上させることができます。 PureComponentReact.memo()を使用すると、アプリでのレンダリング操作の数が減るため、Reactアプリケーションのパフォーマンスが大幅に向上します。

両方が何をするかを理解するために、最初に、値または状態に変化があるかどうかに関係なく、コンポーネントが2秒ごとにレンダリングするコードを確認します。

ExampleClassComponent.js

import React, { Component } from 'react';

function Unstable(props) {
  // monitor how many times this component is rendered
  console.log('Rendered Unstable component');
  return (
    <div>
      <p>{props.value}</p>
    </div>
  );
};

class App extends Component {
  state = {
    value: 1
  };

  componentDidMount() {
    setInterval(() => {
      this.setState(() => {
        return { value: 1 };
      });
    }, 2000);
  }

  render() {
    return (
      <div>
        <Unstable value={this.state.value} />
      </div>
    );
  }
}
export default App;

アプリを実行してログを確認すると、状態や小道具を変更せずに、2秒ごとにコンポーネントがレンダリングされることがわかります。 これは、PureComponentReact.memo()の両方で改善できる状況です。

ほとんどの場合、状態または小道具に変更があった場合にのみ、コンポーネントを再レンダリングする必要があります。 上記の例を使用すると、PureComponentで改善して、状態または小道具に変化があった場合にのみコンポーネントが再レンダリングされるようにすることができます。

これは、PureComponentをインポートして拡張することで実現できます。

ExamplePureComponent.js

import React, { PureComponent } from 'react';

function Unstable(props) {
  console.log('Rendered Unstable component');
  return (
    <div>
      <p>{props.value}</p>
    </div>
  );
};

class App extends PureComponent {
  state = {
    value: 1
  };

  componentDidMount() {
    setInterval(() => {
      this.setState(() => {
        return { value: 1 };
      });
    }, 2000);
  }

  render() {
    return (
      <div>
        <Unstable value={this.state.value} />
      </div>
    );
  }
}

export default App;

これで、アプリを再度実行すると、最初のレンダリングのみが取得されます。 その後は何も起こりません。 これは、class App extends Component {}の代わりにclass App extends PureComponent {}があるためです。

これにより、現在の状態に関係なくコンポーネントが再レンダリングされるという問題が解決されます。 ただし、setStateメソッド内で状態変化を実装すると、別の問題が発生します。

たとえば、setState()に対する次の変更について考えてみます。

現在、value1に設定されています。

componentDidMount() {
  setInterval(() => {
    this.setState(() => {
      return { value: 1 };
    });
  }, 2000);
}

valueMath.random()に設定されている状況を考えてみましょう。

componentDidMount() {
  setInterval(() => {
    this.setState(() => {
      return { value: Math.round(Math.random()) };
    });
  }, 2000);
}

このシナリオでは、最初のサンプルコンポーネントは、値が次のランダムな数値に更新されるたびに再レンダリングされます。 ただし、PureComponentを使用すると、状態または小道具に変更があった場合にのみ、コンポーネントを再レンダリングできます。

これで、React.memo()を使用して同じ修正を行う方法を調べることができます。 これを行うには、コンポーネントをReact.memo()でラップします。

ExampleReactMemo.js

import React, { Component } from 'react';

const Unstable = React.memo(function Unstable (props) {
  console.log('Rendered Unstable component');
  return (
    <div>
      <p>{props.value}</p>
    </div>
  );
});

class App extends Component {
  state = {
    val: 1
  };

  componentDidMount() {
    setInterval(() => {
      this.setState(() => {
        return { value: 1 };
      });
    }, 2000);
  }

  render() {
    return (
      <div>
        <Unstable val={this.state.val} />
      </div>
    );
  }
}

export default App;

この例のCodeSandboxは次のとおりです。

これにより、PureComponentを使用した場合と同じ結果が得られます。 コンポーネントは最初のレンダリング後にのみレンダリングされ、状態または小道具が変更されるまで再レンダリングされません。

結論

このチュートリアルでは、Reactフックを使用して既存のクラスベースのコンポーネントを機能コンポーネントに変換するためのいくつかのアプローチを検討しました。

また、ReactPureComponentクラスをReact.memo()に変換する特殊なケースについても見てきました。

アプリケーションでフックを使用するには、Reactのバージョンをサポートされているバージョンに更新してください。

"react": "^16.7.0-alpha",
"react-dom": "^16.7.0-alpha",

これで、Reactフックをさらに実験するための基盤ができました。

Reactフック入門およびReactフックを使用したReactTo-Doアプリの構築の詳細をご覧ください。