reactjs

[Get started]から始めるReact開発:その4(State and Lifecycle②

引き続きReact

[Get started]から始めるReact開発:その1
[Get started]から始めるReact開発:その2:Rendering Elements
[Get started]から始めるReact開発:その3:Components and Props
[Get started]から始めるReact開発:その4(State and Lifecycle①
[Get started]から始めるReact開発:その4(State and Lifecycle②
あまりに長かったので分割しました。

Adding Lifecycle Methods to a Class(クラスにライフサイクルメソッドを加える

多くのコンポーネントを含むアプリケーションでは、コンポーネントが破棄される時に合わせてリソースの開放をすることが非常に重要です。
Clockは最初にDOMにレンダリングされるときに毎回タイマーを設定しなければいけません。これをReactではmountingと呼びます。
またClockによって作られたDOMが削除された時にタイマーをかいほうしなければいけません。これをReactではunmountingと呼びます。
コンポーネントがmountsunmountsする時に幾つかのコードを実行するための特別なメソッドを宣言できます。

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()フックはDMOMにコンポーネントがレンダリングされたあとに実行されます。これはタイマーをセットするのに最適です。

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

thisにtimerIDを保存する方法については注意してください。
this.propsはリアクトによって設定され、this.stateは特別な意味を持ちますが、視覚的なアウトプットに使用されないようなデータを保存する必要がある場合はクラスに手動で追加のフィールドを加えることは自由にできます。
render()でそのデータが使われない場合は、stateにはないほうがよいでしょう。

componentWillUnmount()ライフサイクルフックではタイマーを破棄します。

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

最後に、毎秒実行するメソッドのtick()を実行します。

コンポーネントのlocal stateの更新を調整するために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')
);

現在時計は毎秒刻みで動きます。

これらがどのように動いているのかということとメソッドが呼ばれる順番をおさらいしましょう。

1) <Clock />ReactDOM.render()に渡された時、リアクトはClockコンポーネントのコンストラクタを呼び出します。Clockは現在の時間を表示する必要があるため、現在の時間を含んだオブジェクトでthis.stateを初期化します。

2) ReactはClockコンポーネントのrender()メソッドを呼び出します。これは何を画面上に表示すべきかをReactに知らせています。Reactはアウトプットに合うようにDOMを更新します。

3) ClockアウトプットがDOMに入れられた時に、ReactはcomponentDidMount()を呼び出します。その中では、Clockコンポーネントが毎秒tick()を呼ぶためのタイマーの設定をブラウザに対して要求しています。

4) 毎秒tick()メソッドがブラウザから呼ばれます。その中では、Clockコンポーネントが現在の時間を含んだオブジェクトでsetState()を呼び出すことでUI更新を調整します。setState()呼び出しに感謝をして、Reactはstateに変更があることを知り、何が画面上に表示されるべきかを再度知るためにrender()メソッドを呼びます。この時、render()メソッドの中のthis.state.dateは異なりますし、描画されるアウトプットは更新された時間も含みます。ReactはそれによってDOMを更新します。

5) ClockコンポーネントがそのうちにDOMから削除された場合は、ReactはcomponentWillUnmount()を呼び出しタイマーをストップします。

Using State Correctly

setState()について知るべきことが3つあります。

Do Not Modify State Directly(Stateは直接変更できない

例えば、下記はコンポーネントでの再レンダリングができません。

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

代わりにsetState()を使用します。

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

this.stateを割り当てることができるただひとつの場所はコンストラクタです。

State Updates May Be Asynchronous(Stateはの更新は非同期に

Reactはパフォーマンスのために一つの更新の中で複数のsetState()呼び出しをバッチできます。
this.statethis.propsは非同期に更新されるので、次のStateを計算するためにそれらの値を使うべきではない。

例えば、下記コードはカウンターの更新に失敗する。

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

これを修正するためにオブジェクトというよりは関数として受け入れるsetState()の第二形式を利用します。
この関数は第一引数として前のstateを受け入れ、第二引数として更新が適用された時のpropsを受け入れます。

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

上記はアロー関数を使用しましたが、通常の関数としても利用できます。

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

State Updates are Merged

ReactではsetState()を呼ぶ時は、現在の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については完全に入れ替えます。

The Data Flows Down

親でも子でもないコンポーネントは、コンポーネントがステートフルかステートレスかを知ることができますし、関数として定義されているかクラスとして定義されているかどうかを気にする必要はありません。
これがStateがよくカプセル化もしくはローカルと呼ばれる理由です。それをセットしたものやそれ自身以外のコンポーネントからは利用できません。
A component may choose to pass its state down as props to its child components.(ちょっといみわからん、というかここからちょっと英語よくわかんなくなったのでほぼ機械翻訳)

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

これはまた、ユーザー定義コンポーネントとして働きます。

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

FormattedDateコンポーネントはdateをpropsで受け入れ、それがClock's stateClock's propsもしくは手で入力されたものかどうかはしりません。

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

CodePenで確認

これは一般的にtop-downもしくはunidirectionalデータフローと呼ばれています。全てのstateは常に特定のコンポーネントに所有され、そのstateから派生した全てのデータやUIはツリー内のコンポーネントの下にのみ影響を与えます。

滝のようなものを想像してください。それぞれのコンポーネントStateがそれぞれの水源に流れていくイメージです。

それぞれのコンポーネントは完全に独立しているようにみえるため、3つの<Clock>をレンダリングしたAppコンポーネントを作ることができます。

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

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

CodePenで確認

それぞれのclockはタイマーのセットと更新を個別に行っています。
React Appsでは、コンポーネントがステートフルかステートレスかどうかは時間とともに変わるかもしれないコンポーネントの詳細な実装を考える必要があります。ステートフルなコンポーネントの内側のステートレスコンポーネントを使うことができますし、その逆もまた可能です。