React コンポーネントの状態(State)

コンポーネントは状態(state)を持つことができます。これはコンポーネントデータのプライベートな部分であり、動的データを管理するために使われます。

状態はクラスコンポーネントにのみ適用されます。ただし React のフックを使用する場合は関数コンポーネントでも使用できます。

React はコンポーネントを状態機械(State Machine)とみなします。ユーザーとの対話を通じて異なる状態を実現し、UI をレンダリングして、ユーザーインターフェースとデータを常に一致させます。

React では、コンポーネントの state を更新して、その新しい state に基づいてユーザーインターフェースを再レンダリングするだけで済みます(DOM を操作する必要はありません)。

次の例では、React.Component を拡張した ES6 クラスを作成し、render() メソッド内で this.state を使って現在の時刻を更新します。

クラスコンストラクターを追加して状態 this.state を初期化します。クラスコンポーネントは常に props を渡して基底コンストラクターを呼び出す必要があります。


クラスコンポーネント内の状態管理

状態を持つクラスコンポーネントを作成します:

Counter.js ファイル

import React, { Component } from 'react';

class Counter extends Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }

  increment = () => {
    this.setState({ count: this.state.count + 1 });
  }

  render() {
    return (
      <div>
        <p>Count: {this.state.count}</p>
        <button onClick={this.increment}>Increment</button>
      </div>
    );
  }
}

export default Counter;

このコンポーネントを src/index.js でレンダリングします:

例

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import Counter from './Counter';

const root = ReactDOM.createRoot(document.getElementById("root"));
// Counter コンポーネントをレンダリング
root.render(<Counter />);

関数コンポーネント内の状態管理(フックを使用)

React フックを使用すると、関数コンポーネントで状態を使用できます。最もよく使われるフックは useState です。

状態を持つ関数コンポーネントを作成します:

Counter.js ファイル

import React, { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

export default Counter;

このコンポーネントを src/index.js でレンダリングします:

例

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import Counter from './Counter';

const root = ReactDOM.createRoot(document.getElementById("root"));
// Counter コンポーネントをレンダリング
root.render(<Counter />);

例

Clock のインスタンスを作成して、コンポーネントの状態を理解しましょう:

React の例

class Clock extends React.Component { constructor(props) { super(props); this.state = {date: new Date()}; } render() { return ( <div> <h1>Hello, world!</h1> <h2>現在の時刻は{this.state.date.toLocaleTimeString()}.</h2> </div> ); } } const root = ReactDOM.createRoot(document.getElementById("root")); root.render( <Clock /> );

試してみる »

次に、Clock に独自のタイマーを設定させ、毎秒更新するようにします。

ライフサイクルメソッドをクラスに追加する

多くのコンポーネントを持つアプリケーションでは、破棄時にコンポーネントが占有しているリソースを解放することが非常に重要です。

Clock コンポーネントが初めて DOM に読み込まれるたびに、タイマーを生成したいと思います。これは React では「マウント」。

」と呼ばれます。同様に、Clock が生成したこの DOM が削除されるたびに、タイマーもクリアしたいと思います。これは React では「アンマウント」。

コンポーネントクラスに特別なメソッドを宣言して、コンポーネントがマウントまたはアンマウントされるときにコードを実行できます:

React の例

class Clock extends React.Component { constructor(props) { super(props); this.state = {date: new Date()}; } componentDidMount() { this.timerID = setInterval( () => this.tick(), 1000 ); } componentWillUnmount() { clearInterval(this.timerID); } tick() { this.setState({ date: new Date() }); } render() { return ( <div> <h1>Hello, world!</h1> <h2>現在の時刻は{this.state.date.toLocaleTimeString()}.</h2> </div> ); } } const root = ReactDOM.createRoot(document.getElementById("root")); root.render( <Clock /> );

試してみる »

例の解説:

componentDidMount()とcomponentWillUnmount()このメソッドはライフサイクルフックと呼ばれます。

コンポーネントの出力が DOM に反映された後に実行されるcomponentDidMount()フックです。このフックでタイマーを設定できます。

this.timerID はタイマーの ID です。次のcomponentWillUnmount()フック内でタイマーをクリアできます。

コードの実行順序:

  1. 当 <Clock />渡されるReactDOM.render()とき、React が呼び出すのはClockコンポーネントのコンストラクターです。そのためClock現在時刻を表示する必要があるので、現在時刻を含むオブジェクトで初期化します。this.stateこの状態は後で更新します。

  2. React が次に呼び出すのはClockコンポーネントのrender()メソッドです。これにより React は画面に何を表示すべきかを把握し、React は DOM をClockのレンダリング出力に一致するように更新します。

  3. 当 Clockの出力が DOM に挿入されるとき、React が呼び出すのはcomponentDidMount()ライフサイクルフックです。その中で、Clockコンポーネントはブラウザにタイマーを設定させ、毎秒 1 回呼び出します。tick()。

  4. ブラウザが毎秒呼び出すのはtick()メソッドです。その中で、Clockコンポーネントは現在時刻を含むオブジェクトを使ってsetState()呼び出して UI 更新をスケジュールします。呼び出すことによって、setState()React は状態が変わったことを知り、再び呼び出すのはrender()メソッドで、画面に何を表示すべきかを決定するためです。今回は、render()メソッド内の値this.state.dateが異なるため、レンダリング出力には更新された時刻が含まれ、それに応じて DOM が更新されます。

  5. いったんClockコンポーネントが DOM から削除されると、React が呼び出すのはcomponentWillUnmount()このフック関数です。タイマーもクリアされます。

データは上から下へ流れる

親コンポーネントも子コンポーネントも、あるコンポーネントが状態を持つか持たないかを知ることはできません。また、あるコンポーネントが関数として定義されているかクラスとして定義されているかを気にするべきでもありません。

これが、状態が通常ローカルまたはカプセル化と呼ばれる理由です。状態を所有し設定するコンポーネント以外は、その状態にアクセスできません。

次の例では、FormattedDate コンポーネントはそのプロパティで date 値を受け取りますが、それが Clock の状態(state)からのものか、Clock のプロパティ(props)からのものか、手動入力によるものかを知りません:

React の例

function FormattedDate(props) { return <h2>現在の時刻は{props.date.toLocaleTimeString()}.</h2>; } class Clock extends React.Component { constructor(props) { super(props); this.state = {date: new Date()}; } componentDidMount() { this.timerID = setInterval( () => this.tick(), 1000 ); } componentWillUnmount() { clearInterval(this.timerID); } tick() { this.setState({ date: new Date() }); } render() { return ( <div> <h1>Hello, world!</h1> <FormattedDate date={this.state.date} /> </div> ); } } const root = ReactDOM.createRoot(document.getElementById("root")); root.render( <Clock /> );

試してみる »

これは通常、上から下への、または一方向のデータフローと呼ばれます。どんな状態も常に特定のコンポーネントによって所有され、その状態から導出されるデータや UI は、ツリーの下位にあるコンポーネントにのみ影響を与えます。

コンポーネントツリーをプロパティの滝(ウォーターフォール)として想像するなら、各コンポーネントの状態は追加の水源のようなもので、任意の地点に接続され、そこから下へも流れていきます。

すべてのコンポーネントが本当に分離されていることを示すために、3 つの Clock をレンダリングする App コンポーネントを作成できます:

React の例

function FormattedDate(props) { return <h2>現在の時刻は{props.date.toLocaleTimeString()}.</h2>; } class Clock extends React.Component { constructor(props) { super(props); this.state = {date: new Date()}; } componentDidMount() { this.timerID = setInterval( () => this.tick(), 1000 ); } componentWillUnmount() { clearInterval(this.timerID); } tick() { this.setState({ date: new Date() }); } render() { return ( <div> <h1>Hello, world!</h1> <FormattedDate date={this.state.date} /> </div> ); } } function App() { return ( <div> <Clock /> <Clock /> <Clock /> </div> ); } const root = ReactDOM.createRoot(document.getElementById("root")); root.render(<App />);

試してみる »

上記の例では、各 Clock コンポーネントが独自のタイマーを設定し、独立して更新されます。

React アプリケーションでは、コンポーネントが状態を持つか持たないかは、時間とともに変わる可能性のあるコンポーネントの実装詳細と見なされます。

状態を持つコンポーネント内で状態を持たないコンポーネントを使用できますし、状態を持たないコンポーネント内で状態を持つコンポーネントを使用することもできます。

その他の拡張