React入門 未経験から1週間でReactをマスターする #06. コンポーネントのライフサイクル

# 目次

[1. Reactの新規プロジェクトの立ち上げ](https://qiita.com/yassun-youtube/items/2ae26050efd2133c2286)
[2. コンポーネントのプロパティ(props)とステート(state)](https://qiita.com/yassun-youtube/items/ca91e2f9905fb8ca62d0)
[3. Class Components と Function Components] (https://qiita.com/yassun-youtube/items/2ed8601e4fa477726705)
[4. 条件分岐 (if) と繰り返し (loop)](https://qiita.com/yassun-youtube/items/9474b1681958e2ab0f25)
[5. フォームと親子間のデータのやり取り](https://qiita.com/yassun-youtube/items/545e7d9c98fe919dc394)
[6. コンポーネントのライフサイクル](https://qiita.com/yassun-youtube/items/372464404ae6855e6d78)<font color="Red">←今ここ</font>
7. スタイル (準備中)
8. Higher-Order Component (準備中)
9. Portalを利用したモーダル (準備中)
10. refによるエレメントの取得 (準備中)
11. Contextを利用したテーマの変更 (準備中)


## 今回の学習内容

今回は、

- Reactのコンポーネントのライフサイクルを理解する
- ライフサイクルごとのメソッドの利用方法を理解する

をやっていきます。

# YouTubeでの解説動画

YouTubeでも解説しています。
動画で確認したい方はこちらもどうぞ。
[【YouTube動画】 未経験から1週間でをマスターするReact入門 #06. コンポーネントのライフサイクル](https://youtu.be/SKrnW7PRBdk)
[<img src="https://img.youtube.com/vi/SKrnW7PRBdk/0.jpg" alt="未経験から1週間でをマスターするReact入門 #06. コンポーネントのライフサイクル">](https://youtu.be/SKrnW7PRBdk)

# この記事のソースコード
ソースコードはGitHubで公開しています。

https://github.com/yassun-youtube/ReactTutorial

今回のコミット一覧↓

![スクリーンショット 2020-12-30 16.20.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/751408/57fe0d28-8b25-65c7-c316-0229c6363782.png)


# コンポーネントのライフサイクル

ライフサイクル、という言葉はプログラミングの世界でよく使われますが、簡単に言うと生まれてから死ぬまでに起こるイベントなどのことです。
今回のコンポーネントのライフサイクルですが、簡単に言うとコンポーネントが画面に表示されてから画面からなくなるまでの間に起こるイベントのことを言います。

Reactのコンポーネントのライフサイクルは下図のようになっています。

![_2020-11-02_0.13.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/751408/c5267f54-c8f0-dccf-30ee-ab856864521c.png)

Mountingが表示されるとき、Updatingは表示の内容が変わる時、Unmountingが画面から消える時、と覚えてください。

例えば、
表示する前に、APIからデータを取って表示する。
表示が終わったらデータを削除する、監視を終了する。

などのようなことはとても良く行われるのですが、プログラム上、画面への表示や画面から消えるのは明示的にコードを書くわけではない（例えば show() みたいなメソッドで表示するわけではない）ので、Reactが起こしてくれたイベントをキャッチしてそこに始めて表示するときに実行したいコードを書くわけですね。

## ライフサイクル用のメソッド

画像の一番下に記載されているメソッドがライフサイクルのメソッドです。

簡単に言うと、

- コンポーネントが初期化されるときに呼び出されるのが `componentDidMount`
- コンポーネントの更新ごとに呼び出されるのが `componentDidUpdate`
- コンポーネントが削除されるときに呼び出されるのが `componentWillUnmount`

になります。

これらのメソッドは `Class Components` でのメソッドになります。

`Class Components` ではこれらのメソッドがそのまま用意されていて、簡単に利用することができます。
`Funciton Component` では、これらの3つのメソッドは `useEffect` という一つのメソッドで表現されます。

## 実際にコードを書いてみる

実際に、 `App.js` が言語データを取得するのを、初期化時に変更してみましょう。

APIでの取得をモックするために、 `const/languages.js` に `function` を追加してみましょう。
`const` じゃなくなってしまいましたが、一旦無視します。

```jsx:const/languages.js
const LANGUAGES = [
  'JavaScript',
  'C++',
  'Ruby',
  'Java',
  'PHP',
  'Go'
];

export const getLanguages = () => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(LANGUAGES);
    }, 1000);
  });
};
```

`getLanguages` という関数を追加しました。

これは実行されて1秒経つと `LANGUAGES` を返すという関数になります。

```jsx:src/App.js
import { useState, useEffect } from 'react'; // 変更
import { List } from "./List";
import { Form } from "./Form";
import { getLanguages } from "./const/languages";

function App() {
  const [tab, setTab] = useState('list');
  const [langs, setLangs] = useState([]);

  useEffect(() => { // 追加
    console.log('App.js:useEffect');
    fetchLanguages(); // APIから取得するイメージ
  }, []);

  const fetchLanguages = async () =>{ // 追加
    const languages = await getLanguages();
    setLangs(languages);
  };

  const addLang = (lang) => {
    setLangs([...langs, lang]);
    setTab('list');
  };

  return (
    <div>
      <header>
        <ul>
          <li onClick={() => setTab('list')}>リスト</li>
          <li onClick={() => setTab('form')}>フォーム</li>
        </ul>
      </header>
      <hr />
      {
        tab === 'list' ? <List langs={langs} /> : <Form onAddLang={addLang}/>
      }
    </div>
  );
}

export default App;
```

これで、表示されて下図のように1秒後に言語のリストが表示されるようになりました。

![61f463c3e17d5d3a7ea133eb7dacea48.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/751408/e09ff9ee-966f-1050-bd80-037a1244aefa.gif)

## コード解説

```jsx
import { useState, useEffect } from 'react';
```

今回は、 `react` から `useEffect` のインポートを追加しています。

```jsx
  useEffect(() => { // 追加
    console.log('App.js:useEffect');
    fetchLanguages(); // APIから取得するイメージ
  }, []);
```

 `useEffect` は、２つの引数を取ります。

１つ目の引数は関数で、２つ目の引数は依存する変数の配列です。

`useEffect` を利用すると、先程説明した `componentDidMount`と `componentDidUpdate` のどちらの場合でも、第一引数の関数が呼び出されます。つまり、初期化時、および状態の変更時に呼び出されます。

ただし、第2引数が与えられるとその値が変更されたときだけ呼び出されるようになります。

ということは、第2引数に空の配列を指定すると `componentDidMount` と同じように動作させることができます。

```jsx
useEffect(()=> {
  // 初期化時、更新時（状態変更時など）に常に呼ばれるが、
}, [count]) // これを指定すると count の変更時のみ実行されるようになる
```

ちょっと触っても理解できないかもしれないので、少し `useEffect`で遊んでみましょう。

## useEffectで遊んでみる

### `useEffect` の第2引数を取り除いてみると…

```jsx:src/App.js
 useEffect(() => {
    console.log('App.js:useEffect');
    fetchLanguages(); // APIから取得するイメージ
  }); // <= ここから配列を取り除いた
```

全体のコードは下記です。

```jsx:src/App.js
import { useState, useEffect } from 'react';
import { List } from "./List";
import { Form } from "./Form";
import { getLanguages } from "./const/languages";

function App() {
  const [tab, setTab] = useState('list');
  const [langs, setLangs] = useState([]);

  useEffect(() => {
    console.log('App.js:useEffect');
    fetchLanguages(); // APIから取得するイメージ
  }); // <= ここから配列を取り除いた

  const fetchLanguages = async () =>{
    const languages = await getLanguages();
    setLangs(languages);
  };

  const addLang = (lang) => {
    setLangs([...langs, lang]);
    setTab('list');
  };

  return (
    <div>
      <header>
        <ul>
          <li onClick={() => setTab('list')}>リスト</li>
          <li onClick={() => setTab('form')}>フォーム</li>
        </ul>
      </header>
      <hr />
      {
        tab === 'list' ? <List langs={langs} /> : <Form onAddLang={addLang}/>
      }
    </div>
  );
}

export default App;
```

こうすると、初期化時と更新時に常に `useEffect` の第1引数の関数が呼び出されるはずですね。

実際に見ていきましょう。

![40d3cb1ec216b9f4fddcb22e3374878c.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/751408/a3e76ba8-55bb-34f9-60fc-5f9ae25c79b2.gif)

上図のように、タブを切り替えるだけでも `useEffect` の第1引数の関数が呼び出されていることがわかります。

上図の動作を解説すると、

1. 初期化時
2. 1秒後に `setLangs` 実行による更新
3. フォームを押して `setTab` 実行による更新
4. リストを押して `setTab` 実行による更新
5. フォームを押して `setTab` 実行による更新

の5回の呼び出しがありました。

もう少し遊んでみます。

## `useEffect` の第2引数に `langs` を入れてみる。

```jsx:src/App.js
import { useState, useEffect } from 'react';
import { List } from "./List";
import { Form } from "./Form";
import { getLanguages } from "./const/languages";

function App() {
  const [tab, setTab] = useState('list');
  const [langs, setLangs] = useState([]);

  useEffect(() => {
    console.log('App.js:useEffect');
    fetchLanguages(); // APIから取得するイメージ
  }, [langs]); // ここを [langs] に変更

  const fetchLanguages = async () =>{
    const languages = await getLanguages();
    setLangs(languages);
  };

  const addLang = (lang) => {
    setLangs([...langs, lang]);
    setTab('list');
  };

  return (
    <div>
      <header>
        <ul>
          <li onClick={() => setTab('list')}>リスト</li>
          <li onClick={() => setTab('form')}>フォーム</li>
        </ul>
      </header>
      <hr />
      {
        tab === 'list' ? <List langs={langs} /> : <Form onAddLang={addLang}/>
      }
    </div>
  );
}

export default App;
```

今度はこうなります。

![2c74734a6721d0fe8f21272b809be3c8.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/751408/37e1c685-606f-5847-a63b-e9b6080770f5.gif)

今度はタブの切り替えでは実行されなくなったのがわかります。

さらにもう一つやってみましょう。

## `useEffect` の第2引数に `tab` を入れてみる。

```jsx:src/App.js
import { useState, useEffect } from 'react';
import { List } from "./List";
import { Form } from "./Form";
import { getLanguages } from "./const/languages";

function App() {
  const [tab, setTab] = useState('list');
  const [langs, setLangs] = useState([]);

  useEffect(() => {
    console.log('App.js:useEffect');
    fetchLanguages(); // APIから取得するイメージ
  }, [tab]); // ここを [tab] に変更

  const fetchLanguages = async () =>{
    const languages = await getLanguages();
    setLangs(languages);
  };

  const addLang = (lang) => {
    setLangs([...langs, lang]);
    setTab('list');
  };

  return (
    <div>
      <header>
        <ul>
          <li onClick={() => setTab('list')}>リスト</li>
          <li onClick={() => setTab('form')}>フォーム</li>
        </ul>
      </header>
      <hr />
      {
        tab === 'list' ? <List langs={langs} /> : <Form onAddLang={addLang}/>
      }
    </div>
  );
}

export default App;
```

![505bdac4a8debd0b372ccbce2a5ef88f.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/751408/c7ac65a2-9ee4-010b-6751-5709b2063c47.gif)


今度は更新時に 2回実行されるところが実行されなくなり、タブの変更での実行だけになりました。

そろそろ`useEffect` の動きがわかってきたでしょうか。

## ComponentWillUnmount

`useEffect` を使って `componentDidMount` と `componentDidUpdate` 、つまり初期化と更新時の挙動を見ていきました。

しかし、最初に紹介したライフサイクルは3つありました。

最後の一つ、 `componentWillUnmount` つまりコンポーネント削除時のライフサイクルが残っています。

`Function Components`では、 `useEffect` の第一引数の返り値が `componentWillUnmount` と同等の動きをします。

実際にコードを見てみるとわかりやすいです。

`List.js` にコードを追加してみます。

```jsx:src/List.js
import { useEffect } from 'react'; // 追加

export const List = ({ langs }) => {
  useEffect(() => { // 追加
    console.log('List.js:useEffect');

    return () => { // ここが componentWillUnmount
      console.log('List.js:useEffect:unmount')
    }
  });

  return (
    <div>
      {
        langs.map((lang, index) => {
          return <div key={index}>{ lang }</div>
        })
      }
    </div>
  )
};
```

全体のコードはこちらですが、 `useEffect` に集中して見てみましょう。

```jsx
  useEffect(() => { // 追加
    console.log('List.js:useEffect');
  
	  return () => { // ここが componentWillUnmount
      console.log('List.js:useEffect:unmount')
    }
  });
```

`useEffect` の第1引数の関数の `return` で関数を返しているのがわかるでしょうか。

これがコンポーネント削除時に実行されます。

実際の動きを見てみましょう。

![6209c551c854f15b6a8f2f10399e29c5.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/751408/a7839eee-8f89-5c96-bc1d-5e355a3425a4.gif)

タブが変わるとコンソールにログが出ていることがわかります。

`List.js` は、タブの状態で表示されたり表示されなくなったりします。

この表示されなくなったタイミング（フォームが押されたタイミング）で `List.js:useEffect:unmount`という文言が表示されていますね。

# 今日やったこと1

## コンポーネントのライフサイクルを理解する

```jsx
初期化時 => componentDidMount, useEffect
更新時(new props, setState, forceUpdate) => componentDidUpdate, useEffect
終了時 => componentWillUnmount, useEffect(第1引数の返り値)
```

## function componentsでは useEffect

```jsx
useEffect(() => {
  // mounting, updating時に呼ばれる
})
```

```jsx
useEffect(() => {
  // mounting時に呼ばれる
}, [])
```

```jsx
useEffect(() => {
  // mounting, langsの変更時に呼ばれる
}, [langs])
```

```jsx
useEffect(() => {
  // mounting, updating時に呼ばれる
  return () => {
    // unmounting時に呼ばれる
  }
})

```

## class componentsではそれぞれのメソッド

```jsx
class Test extends React.Component {
  componentDidMount() {
    // mounting時に呼ばれる
  }
  componentDidUpdate() {
    // updating時に呼ばれる
  }
  componentWillUnmount() {
    // unmounting時に呼ばれる
  }
}
```

# もっとconsoleで遊んでみる

各コンポーネントの最初に `console.log(<コンポーネントのファイル名>)` を入れてみましょう。

```jsx:src/App.js
import { useState, useEffect } from 'react';
import { List } from "./List";
import { Form } from "./Form";
import { getLanguages } from "./const/languages";

function App() {
  console.warn('App.js'); // ここを追加
  const [tab, setTab] = useState('list');
  const [langs, setLangs] = useState([]);

  useEffect(() => {
    console.log('App.js:useEffect');
    fetchLanguages(); // APIから取得するイメージ
  }, []);

  const fetchLanguages = async () =>{
    const languages = await getLanguages();
    setLangs(languages);
  };

  const addLang = (lang) => {
    setLangs([...langs, lang]);
    setTab('list');
  };

  return (
    <div>
      <header>
        <ul>
          <li onClick={() => setTab('list')}>リスト</li>
          <li onClick={() => setTab('form')}>フォーム</li>
        </ul>
      </header>
      <hr />
      {
        tab === 'list' ? <List langs={langs} /> : <Form onAddLang={addLang}/>
      }
    </div>
  );
}

export default App;
```

```jsx:src/Form.js
import { useState } from 'react';

export const Form = ({ onAddLang }) => {
  console.warn('Form.js'); // ここを追加
  const [text, setText] = useState('');

  const submitForm = (e) => {
    e.preventDefault();
    onAddLang(text);
  }

  return (
    <div>
      <h4>新しい言語の追加</h4>
      <form onSubmit={submitForm}>
        <div>
          <input type="text" value={text} onChange={(e) => setText(e.target.value)} />
        </div>
        <div>
          <button>追加</button>
        </div>
      </form>
    </div>
  )
}
```

```jsx:src/List.js
import { useEffect } from 'react';

export const List = ({ langs }) => {
  console.warn('List.js'); // ここを追加

  useEffect(() => {
    console.log('List.js:useEffect');
    return () => {
      console.log('List.js:useEffect:unmount')
    }
  });

  return (
    <div>
      {
        langs.map((lang, index) => {
          return <div key={index}>{ lang }</div>
        })
      }
    </div>
  )
};
```

コンソールがどのようになるか見てみましょう。
![ab598cb63d6823271010a8bfda484ee7.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/751408/e83f173b-2ed4-a387-ced1-f2897bf54beb.gif)

表示がかわるたびに `App.js` と 子コンポーネントの `console.warn` が呼ばれていることがわかります。

`Function Component` はただの `function` なので、表示されるたびに呼び出されているんですね。

面白いです。

# コード整理

一旦今日記載したコードから今後不要と思われるコードを消していきます。

```jsx:src/List.js
export const List = ({ langs }) => {
  return (
    <div>
      {
        langs.map((lang, index) => {
          return <div key={index}>{ lang }</div>
        })
      }
    </div>
  )
};
```

`List.js` をもとに戻しました。

# Class Components

`Class Components` では、ライフサイクルのメソッドが用意されており、それをクラスで定義することで利用できます。

```jsx:src/App.js
import React from 'react';
import { List } from "./List";
import { Form } from "./Form";
import { getLanguages } from "./const/languages";

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      tab: 'list',
      langs: [], // 変更
    };
  }
  componentDidMount() { // 追加
    this.fetchLanguages();
  }
  async fetchLanguages() { // 追加
    const langs = await getLanguages();
    this.setState({ langs })
  }
  addLang(lang) {
    this.setState({
      langs: [...this.state.langs, lang],
      tab: 'list',
    });
  }
  render() {
    const { tab, langs } = this.state;
    return (
      <div>
        <header>
          <ul>
            <li onClick={() => this.setState({ tab: 'list' })}>リスト</li>
            <li onClick={() => this.setState({ tab: 'form' })}>フォーム</li>
          </ul>
        </header>
        <hr />
        {
          tab === 'list' ? <List langs={langs} /> : <Form onAddLang={(lang) => this.addLang(lang)}/>
        }
      </div>
    )
  }
}

export default App;
```

更新時に呼ばれる `componentDidUpdate` や削除時に呼ばれる `componentWillUnmount` も同様にメソッドを定義することで記載できます。

# おわりに

今回はライフサイクルを学んでいきました。
これでライフサイクルまでちゃんとわかってくると、もう大抵のことができるようになったと言っても良いかもしれません。