序章

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 オブジェクトと呼び出し setState() 更新する新しい値がある場合 name 変数:

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—複数の状態プロパティを持つクラスにフックを追加する

1つの状態プロパティを次のように変換する方法を見てきました useState、ただし、複数の状態プロパティがある場合、同じアプローチは完全には機能しません。 たとえば、次の2つ以上の入力フィールドがある場合 userName, firstName、 と lastName、次に、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;

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

次に、Reactを使用してこのクラスを機能コンポーネントに変換します useStateuseEffect フック:

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—状態のあるクラスにフックを追加します。 componentDidMount、 と componentDidUpdate

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

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

現在、ヘッダーには This is a Class Component. ここで、を定義します componentDidMount() ヘッダーを更新して言うメソッド Welcome to React Hooks 3秒後:

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 に変更します Welcome to React Hooks 3秒後。 これは古典的です componentDidMount() 後に実行されるための動作 render 関数は正常に実行されます。

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

これを実現するには、を実装する必要があります 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;

ここに、あなたは state, componentDidMount()、 と componentDidUpdate(). アプリを実行すると、 componentDidMount() 関数はヘッダーをに更新します Welcome to React Hooks 3秒後。 ヘッダーテキスト入力フィールドに入力を開始すると、 <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.Component を実装していません shouldComponentUpdate() ライフサイクル方式 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 PureComponent {} それ以外の class App extends Component {}.

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

たとえば、次の変更を検討してください。 setState():

現在、 value に設定 1:

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

状況を考えてみましょう value に設定されています Math.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フックを使用して既存のクラスベースのコンポーネントを機能コンポーネントに変換するためのいくつかのアプローチを検討しました。

Reactを変換する特別なケースも見てきました PureComponent クラスから React.memo().

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

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

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

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