stateとライフサイクル

クラスに変換

5ステップで関数コンポーネントをクラスコンポーネントに変換する。

https://ja.reactjs.org/docs/state-and-lifecycle.html#converting-a-function-to-a-class

      function Clock(props) {
        return (
          <div>
            <h1>Hello, world!</h1>
            <h2>It is {new Date().toLocaleTimeString()}.</h2>
          </div>
        );
      }
  1. React.Componet を継承する同名の ES6 クラスを作成
  2. render() と呼ばれる空のメソッドを 1 つ追加
  3. 関数の中身を render() メソッドに移動
  4. render() 内の propsthis.props に書き換え
  5. 空になった関数を削除
class Clock extends React.Component {
  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.props.date.toLocaleTimeString
()}.</h2>
      </div>
    );
  }
}

同一の DOM ノード内で Clock コンポーネントをレンダーしている限り、 Clock クラスのインスタンスは1つだけ使われる。

ローカル state やライフサイクルメソッドなどが利用できるようになる。

state を追加する

3ステップで dateprops から state に移す。

  1. render() メソッド内の this.props.datethis.state.date に書き換え
  2. this.state の初期状態を設定するコンストラクタを追加
  3. <Coloc /> 要素から date プロパティを削除
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')
);

現在の時刻と共に「更新されない」ページが表示された。

ふとした疑問が、「現在の時刻」とは、 DOMContentLoaded イベントだろうか?または、 load イベントだろうか?

初心者な私は確認方法がわからない。

ライフサイクルメソッドを追加

マウント : 最初にコンポーネントが DOM として描画されるとき。出力が DOM にレンダーされた「後」に実行される アンマウント : コンポーネントが生成した DOM が削除されるとき

  componentDidMound() {
    //
  }
  componentWillUnmound() {
    //
  }

タイマーをセットアップするのは、 componentDidMound() ライフサイクルメソッド内です。

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

タイマーの後片付けは componetWillUnmount() ライフサイクルメソッド内で行う。

componentWillUnmound() {
  clearInerval(this.timerID)
}

コンポーネントのローカル state 更新を行うために this.setState() を追加する。

tick() {
  this.setState({
    date: new Date(),
  });

毎秒ごとに時間を刻み続ける。

メソッドが呼び出される順序にそって簡単に振り返ってみましょう https://ja.reactjs.org/docs/state-and-lifecycle.html#adding-lifecycle-methods-to-a-class

丁寧な解説がなされている。コンポーネントの一生について疑問が生じた際は、読みにいくことになりそう。

state を正しく利用する

setState() の3つの重要な事柄

state を直接変更しない

this.state.comment = 'Hello';

代わりに setState() を使う

this.setState({comment: 'Hello'})

時計の例では次のように使用しました。

  tick() {
    this.setState({
      date: new Date()
    });
  }

でも待ってください。 constructor では、次のように書いています。

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

初期化時は例外なのだろうか?

this.state に直接代入してよい唯一の場所はコンストラクタです。

読み落とすところでした。

更新は非同期に行われる可能性がある

this.propsthis.state は非同期に更新されるため、それらの値に依存してはならない。

更新に失敗することのあるコード

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

オブジェクトではなく、関数を受け取る setState() を使う。

setState()

  • 第1引数:直前の state
  • 第2引数:更新が適用される時点での props
this.setState((state, props) => ({
  counter: state.counter + props.increment
}));

更新はマージされる

setState() を呼び出した場合、与えられたオブジェクトを現在の state にマージする。

JavaScript Primer で学んだ オブジェクトのマージ 。ほとんど理解できなかった嫌な記憶が蘇った。

別々の setState() 呼び出しで、変数を独立して更新できる。

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

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

以上が setState() の3つの重要な事柄だそうです。

データは下方向に伝わる

コンポーネントはその子コンポーネントprops として自身の state を渡してよい。

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

FormattedDate コンポーネントprops 経由で date を受け取るが、Clock クラスの state から来たのか、 props から来たのかわからない。

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

単一方向 とか 単一方向データバインディング と呼ばれる。

3つの<Clock> コンポーネントをレンダーする App コンポーネントを作成。

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

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

<Clock /> はそれぞれインスタンスを作るので、独立はしているはず。カプセル化もされているはず。

ステートレスなコンポーネントをステートフルなコンポーネントの中で使うことが可能であり、その逆も同様です。

時代は Functionコンポーネント

React は現時点では、 Hooks を使って Functionコンポーネントを定義することが一般的です。

Clock クラスコンポーネントをファンクションコンポーネントに書き換えに挑戦します。

時計の現在の時刻と、それを更新するために useStae が必要になります。

useState

ステートフルな値と、それを更新するための関数を返します。

const [date, setDate] = React.useState(new Date());

useEffect

副作用を有する可能性のある命令型のコードを受け付けます。 DOM の書き換え、データの購読、タイマー、ロギング、あるいはその他の副作用を、関数コンポーネントの本体(React のレンダーフェーズ)で書くことはできません。 代わりに useEffect を使ってください。

useEffect 時とともに DOM の書き換えが必要なタイマーのためにある。間違いない!

tick() 関数を1秒間隔で呼び出します。

React.useEffect(() => {
  const timerID = setInterval(() => tick(), 1000);

Clock コンポーネントDOM から削除される場合を考慮した処理を追加します。

公式サイトで指摘されなければ、削除処理が必要なことに気づかないだろうな!

  return function cleanup() {
    clearInterval(timerID);
  };

tick() 関数を実装します。現在時刻の状態を更新する必要があるので、 setDate() を利用する。

const tick = () => setDate(new Date());

Clock クラスでは、レンダーの中では statedate 属性でした。

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

Clock ファンクションでは、 date は次のように書くことができます。 dateuseState() で定義されたので、 ステートそのものだからでしょうか?

わかっていない。

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

完成したコードを掲載する。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Hello World</title>
    <script src="https://unpkg.com/react@17/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>

    <!-- Don't use this in production: -->
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
  </head>
  <body>
    <div id="root"></div>
    <script type="text/babel">
      const Clock = () => {
        const [date, setDate] = React.useState(new Date());

        // componentDidMountとcomponentWillUnmountを置き換え
        React.useEffect(() => {
          const timerID = setInterval(() => tick(), 1000);
          return async function cleanup() {
            await clearInterval(timerID);
          };
        });

        // 更新関数に現在の時刻を渡す
        const tick = () => setDate(new Date());

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

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