06. State and Lifecycle - accgetter/React GitHub Wiki

このセクションでは、本当に再利用でき、カプセル化されたクロックコンポーネントの作り方を学びましょう。
タイマーをセットし、1秒ごとに更新します。
code:

function Clock(props) {
  return (
    <div>
      <h1>Hello, world!</h1>
      <h2>It is {props.date.toLocaleTimeString()}.</h2>
    </div>
  );
}

function tick() {
  ReactDOM.render(
    <Clock date={new Date()} />,
    document.getElementById('root')
  );
}

setInterval(tick, 1000);

しかしながら、極めて重要な要求: タイマーを表示UIを更新するそのClockの要素はクロックの詳細としてインプリメンテーションすべきであることがかけています。

理想的には、一回の記述でClockコンポーネントが自分で更新して欲しいです。

ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);

この要求を満たすために、"state"をClockコンポーネントに加える必要があります。
ステートはpropsに似ていますが、これは閉じられていて完全にコンポーネントにコントロールされます。

クラスとして定義されたコンポネーントは幾つかの特徴があると述べました。
ローカルのステートは厳密にコンポーネントのみで有効な機能です。

次の5ステップで、Clockのようなファンクショナルなコンポーネントをクラスにコンバートできます。

  1. React.Componentを継承し、ES6 クラスを同じ名前で作成する
  2. render()メソッドを追加する。
  3. ファンクションの中身をrender()メソッドの中に移す。
  4. render()メソッドの中で、propsをthis.propsにする。
  5. 残った空のファンクションを削除する。
class Clock extends React.Component {
  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.props.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}
function tick() {
  ReactDOM.render(
    <Clock date={new Date()} />,
    document.getElementById('root')
  );
}
setInterval(tick, 1000);

Clockは今、ファンクションというよりClassとして宣言されています。 これで、ローカルステート、ライフサイクルフックという機能を使うことができます。

ローカルステートをクラスに追加する

次の手順で、日付をpropsからstateに移します。

  1. render()メソッド内の"this.props.date"を"this.state.date"に変換します。
class Clock extends React.Component {
  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}
  1. initial this.state をアサインするコンストラクタをクラスに追加
class Clock extends React.Component {

  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

Note ベースコンストラクタにpropsをどのように渡しているか

super(props);

クラスコンポーネントは常に、ベースコンストラクタにpropsを渡して呼ぶ必要があります。 3) date propsを要素から削除します。

ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);

後で、タイマーのcodeをコンポーネント自身に追加します。 結果としてこのようになります。

class Clock extends React.Component {

  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);

次は自ら1秒ごとに時間を更新するClockコンポーネントを作っていきましょう

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

多くのコンポーネントを伴うアプリケーションでは、 コンポーネントが破棄された際に、そのリソースを解放することがとても重要です。 Clockコンポーネントが最初に描画される時は必ずタイマーをセットしたいです。 Reactではこれを"mounting"と呼びます。 また、Clockコンポーネントが生成したDOMが削除された時には、タイマーをクリアしたいです。 Reactではこれを"unmounting"と呼びます。

コンポーネントがmount, unmountする時にいつくかのコードを実行するために特別なメソッドを定義することができます。

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  componentDidMount() {

  }

  componentWillUnmount() {

  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

これらのメソッドを"lifecycle hooks"と呼びます。

componentDidMount() は、 コンポーネントがDOMに描画された後にフックします。 タイマーをセットするにはとてもいい場所です。

 componentDidMount() {
    this.timerID = setInterval(
      () => this.tick(),
      1000
    );
  }

どうやってtimer IDを保持するか this.props はReactそれ自身にセットアップされ、this.state は特別な意味を持っているが、必要であれば手動でクラスにフィールド(この例ではtimerId)を自由に追加してもOK render()メソッドで使わないなら、stateの中にいる必要もありません。

タイマーはcomponentWillUnmount() lifecycle hookで引き剝がしましょう。

  componentWillUnmount() {
    clearInterval(this.timerID);
  }

最後に、毎秒実行されるtick()メソッドを実装します。 これは、コンポーネントのローカルステートのためにthis.setState()メソッドで、更新を定期的におこなう

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>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);

これでClockは毎秒時を刻みます。

何が行われたか、メソッドが呼ばれた順に振り返ってみましょう。

  1. がReactDOM.render()が渡された時、ReactがClockコンポーネントのコンストラクタを呼びます。Clackは現在時刻を表示する必要がありますので、 コンストラクタは、現在時刻を持つオブジェクトでthis.stateを初期化します。 その後そのstateを更新します。

  2. ReactはそれからClockコンポーネントのrender()メソッドをコールします。 これはReactが画面に何を表示すべきか知る方法です。 そしてReactはDOMをClockコンポーネントが出力する時刻表示に更新します。

  3. ClockコンポーネントがDOM内に出力した時、Reactは"componentDidMount() lifecycle hook"を呼びます。 その中で、Clockコンポーネントはブラウザに1秒ごとに時を刻むためにタイマーをセットすることを要求します。

  4. 毎秒ブラウザはtick()メソッドを呼びます。その中で、現在時刻を持ったオブジェクトをsetState()メソッドに渡すことでClockコンポーネントはUIを更新します。 setState()メソッドさまさまです、Reactは画面が最新であるために、stateに変化があるとrender()メソッドが再びコールされることを知っています。 今回、render()メソッドの中のthis.stete.dateは変化し、最新の時間が表示されます。ReactがDOMを適切に更新するのです。

  5. もしClockコンポーネントがDOMから削除されたら、Reactはタイマーをストップするために、"componentWillUnmount() lifecycle hook"をコールします。

Stateを正しく使う

setState() メソッドについて3つ知っておくべきことがあります。

Stateを直接変更していけません

例えば、これはコンポーネントを再描画しません:

// Wrong
this.state.comment = 'Hello';

代わりにsetState()を使ってください:

// Correct
this.setState({comment: 'Hello'});

this.stateに値を代入できるのはコンストラクタのみです。

Stateは非同期で更新されることがある

Reactはパフォーマンスのため、複数のsetState()の呼び出しを一つの更新にまとめるようです。 this.propsとthis.stete非同期的に更新されるので、 stateの値を使って新たなstateを作るような計算はすべきではないです。 例えば、次のコードはカウンターを更新するには失敗します:

// Wrong
this.setState({
  counter: this.state.counter + this.props.increment,
});

これを解決するためには、fanctionを受け取る、setStateの2つ目の形式を使いましょう。 そのfuncitionは第一引数に事前のstateを受け取り, その時のpropsを第二引数として受け取ります:

// Correct
this.setState((prevState, props) => ({
  counter: prevState.counter + props.increment
}));

上記では、arrow関数を使っています。通常の関数でもOKです:

// Correct
this.setState(function(prevState, props) {
  return {
    counter: prevState.counter + props.increment
  };
});

Stateの更新はマージされる

setState()を呼ぶ時、Reactはcurrent stateに提供するオブジェクトをマージします。 例えば、stateは幾つかの独立した値を含んでいます:

  constructor(props) {
    super(props);
    this.state = {
      posts: [],
      comments: []
    };
  }

そして、setState()を分離してコールし、それぞれ独立して更新することができます:

  componentDidMount() {
    fetchPosts().then(response => {
      this.setState({
        posts: response.posts
      });
    });

    fetchComments().then(response => {
      this.setState({
        comments: response.comments
      });
    });
  }

マージは浅い階層で、したがって、this.setState({comments}) はthis.state.postsをそのまま残しますが、this.state.commentsは完全に置き換えられます。

データは下に移動する

親コンポーネントも子コンポーネントも、特定のコンポーネントがstatefulかstatelessかどうか知ることができず、 それらは、それがfuncionなのかclassなのか気にする必要がありません。 それはstateが大抵ローカルで呼ばれるか、カプセル化されるためです。 それを所有し設定する自分自身意外のコンポーネントにはアクセスできません。 コンポーネントは、stateを子コンポーネントのpropsとして渡すことを選択できます:

<h2>It is {this.state.date.toLocaleTimeString()}.</h2>

これはユーザ定義のコンポーネントでもまた効果があります:

<FormattedDate date={this.state.date} />

このFormattedDateコンポーネントは、自分のpropsから日付を受け取り、それがClockコンポーネントのstateか、propsかまたは、手でタイプされたからきどうかは知ることはないでしょう:

function FormattedDate(props) {
  return <h2>It is {props.date.toLocaleTimeString()}.</h2>;
}

これは一般に"top-down" data flow や "unidirectional" data flow と呼ばれます。一方向という意味です。 あらゆるstateが常に幾つかの明確なコンポーネントに所有され、それらのstateより派生したデータまたUIは、下層コンポーネントだけに影響を与えます。

If you imagine a component tree as a waterfall of props, each component's state is like an additional water source that joins it at an arbitrary point but also flows down.

To show that all components are truly isolated, we can create an App component that renders three s:

もしコンポーネントの階層を、propsのウォーターフォールとイメージすると、それぞれのコンポーネントのstateは好きな時に追加される水源のようなもので、 それもまた、下に落ちていきます。

すべてのコンポーネントが本当に独立しているのか見るために、3つのClockコンポーネントを描画するアプリを作ってみましょう。

function App() {
  return (
    <div>
      <Clock />
      <Clock />
      <Clock />
    </div>
  );
}

ReactDOM.render(
  <App />,
  document.getElementById('root')
);

それぞれのClockは自分自身のタイマーをセットし独立して更新しています。 React のアプリケーションでは、コンポーネントがステートフルから否かにかかわらず、徐々に変化するだろうコンポーネントの開発と思えます。 逆もしかりです。

⚠️ **GitHub.com Fallback** ⚠️