Next.js と TypeScript で、React のライフサイクルの仕組みを理解する

TwitterFacebookHatena

TL;DR

このページでは、React のライフサイクルの仕組みの実装方法について解説しますね。一言でいうと React のライフサイクルとは、React コンポーネントが生成され、更新され、最終的に破棄されるまでの流れを表すものです。

開発環境 バージョン
Next.js 13.4.4
TypeScript 5.0.4
Emotion 11.11.0
React 18.2.0

React のライフサイクルとは?

React のライフサイクルとは、React コンポーネントが生成され、更新され、最終的に破棄されるまでの流れを表す考え方です。この流れを理解することで、React コンポーネントがどのように動作するのか、いつどのような処理を書くべきなのかがわかります。

React のライフサイクルは大きく分けて以下の 3 つのフェーズに分類されます。

  • マウントフェーズ(Mounting):コンポーネントが DOM に挿入されるフェーズ
  • 更新フェーズ(Updating):コンポーネントが再レンダリングされるフェーズ
  • アンマウントフェーズ(Unmounting):コンポーネントが DOM から削除されるフェーズ

それぞれのフェーズで特定のライフサイクルメソッドが呼び出されます。以下に基本的なソースコードを示します。

type Props = {
  message: string
}

const Component = ({ message }: Props) => {
  // マウントフェーズ
  React.useEffect(() => {
    console.log('Component mounted')

    // アンマウントフェーズ
    return () => {
      console.log('Component will unmount')
    }
  }, [])

  // 更新フェーズ
  React.useEffect(() => {
    console.log('Component updated')
  })

  return <div>{message}</div>
}

このコードでは useEffect フックを使って、マウントフェーズとアンマウントフェーズ、更新フェーズを示しています。ここで重要なのは、useEffect の第二引数に空の配列 [] を渡していることです。これにより、このエフェクトはコンポーネントがマウントされた時にのみ(そしてクリーンアップ関数はアンマウント時にのみ)実行されます。

React のライフサイクルは、React コンポーネントが生成(マウント)されてから破棄(アンマウント)されるまでの一連のステージを表しています。これらのステージは特定のメソッド、いわゆる「ライフサイクルメソッド」を通じて制御され、それぞれのステージで特定の処理を実行することが可能です。

React のライフサイクルは主に 3 つのフェーズに分けられます:

  1. マウント(Mounting) - コンポーネントが DOM に最初に作成されるフェーズです。このフェーズでは以下のメソッドが順に呼び出されます:
  • constructor()
  • static getDerivedStateFromProps()
  • render()
  • componentDidMount()
  1. 更新(Updating) - プロパティの変更やステートの更新によりコンポーネントが再レンダリングされるフェーズです。以下のメソッドがこのフェーズで呼び出されます:
  • static getDerivedStateFromProps()
  • shouldComponentUpdate()
  • render()
  • getSnapshotBeforeUpdate()
  • componentDidUpdate()
  1. アンマウント(Unmounting) - コンポーネントが DOM から削除されるフェーズです。このフェーズでは次のメソッドが呼び出されます:
  • componentWillUnmount()

これらのライフサイクルメソッドは、特定のタイミングで特定の処理を実行するために使用します。たとえば、API からデータを取得する処理は通常、componentDidMount()メソッドで行います。これは、このメソッドがコンポーネントが DOM に初めてマウントされた直後に一度だけ呼び出されるためです。

なお、上記はクラスコンポーネントのライフサイクルについて説明したものですが、関数コンポーネントでは React Hooks(特にuseStateuseEffect)を用いて同等の処理を実装することができます。それぞれのライフサイクルメソッドの詳細な動作や、それぞれのメソッドがいつ、どのように使用されるべきかを理解することは、React の理解を深めるために重要です。

マウント時のフェーズ

React のライフサイクルのマウント時のフェーズは、次の順序で進行します:

  1. constructor:コンポーネントの初期化時に呼ばれます。主に状態の初期化やイベントハンドラのバインディングなどが行われます。

  2. render:React コンポーネントのレンダリングを行います。コンポーネントが描画すべき内容を返します。このメソッド内では状態の更新や副作用を持つ処理を行ってはいけません。

  3. React が DOM を更新する:render メソッドで返された結果に基づき、実際の DOM が更新されます。

  4. componentDidMount:コンポーネントが DOM にマウントされた後に呼ばれるメソッドです。ここで外部リソースの取得やサブスクリプションの設定などの作業を行います。

以上のフェーズがマウント時のライフサイクルです。ただし、これらのメソッドは React の古いライフサイクル API に基づくもので、現在では Hooks API を使ってこれらの機能をよりシンプルに実装することが推奨されています。例えば、componentDidMount の機能は useEffect フックに空の依存配列 [] を渡すことで同じように実装することができます。

React のライフサイクルを、例えば人間の一日にたとえてみましょう。

  1. constructor:これは一日の始まり、朝起きるときと似ています。目を覚まして、これからの一日の準備をします。React では、ここで初期設定を行います。

  2. render:これは一日の中で、学校や仕事で過ごす時間と似ています。コンポーネントが何を描画すべきかを決めます。これは、どのように一日を過ごすかを決めることに似ています。

  3. React が DOM を更新する:これは、あなたが計画した通りに一日を過ごすことと似ています。描画すべき内容が決まった後、その内容が実際に画面上に表示されます。

  4. componentDidMount:これは一日の終わり、寝る前の時間と似ています。一日を振り返って、次の日のための準備をします。React では、コンポーネントが表示された後に何か追加の作業が必要な場合に、ここで行います。

以上のように、React のライフサイクルは一日の過ごし方に似ています。朝起きて一日が始まり(constructor)、どう過ごすかを決め(render)、その通りに一日を過ごし(DOM 更新)、寝る前に次の日の準備をする(componentDidMount)。これが、React コンポーネントが生まれてから表示されるまでの流れです。

Next.js で、React のライフサイクルを実装

それでは、Next.js を使って React のライフサイクルを具体的に実装してみましょう。以下は pages/index.tsx のソースコードです。

import React from 'react'

type Props = {
  message: string
}

const Home = ({ message }: Props) => {
  // マウントフェーズ
  React.useEffect(() => {
    console.log('Component mounted')

    // アンマウントフェーズ
    return () => {
      console.log('Component will unmount')
    }
  }, [])

  // 更新フェーズ
  React.useEffect(() => {
    console.log('Component updated')
  })

  return <div>{message}</div>
}

Home.getInitialProps = () => {
  return { message: 'Hello, World!' }
}

export default Home

このコードでは、Next.js のページコンポーネント Home が定義されています。そしてその中で React のライフサイクルを useEffect フックを用いて実装しています。getInitialProps メソッドを使って、サーバサイドで初期プロパティを取得しています。

React のライフサイクルを実装

さらに高度な例として、ライフサイクル中で状態を管理し、API からデータを取得して表示する例を見てみましょう。この例では、useEffectuseState フックを使います。

import React, { useState, useEffect } from 'react'
import axios from 'axios'

type Data = {
  id: number
  title: string
}

const Component = () => {
  const [data, setData] = useState<Data[]>([])

  // マウントフェーズ
  useEffect(() => {
    const fetchData = async () => {
      const result = await axios.get('https://jsonplaceholder.typicode.com/posts')
      setData(result.data)
    }
    fetchData()

    // アンマウントフェーズ
    return () => {
      setData([])
    }
  }, [])

  return (
    <div>
      {data.map((item) => (
        <div key={item.id}>{item.title}</div>
      ))}
    </div>
  )
}

このコードでは、useState フックを使って data 状態を管理し、useEffect フックの中で非同期関数 fetchData を定義してデータを取得しています。そして、取得したデータを setData で更新しています。これにより、コンポーネントがマウントされたときに一度だけデータの取得が行われ、その結果がコンポーネントの状態として保存され、レンダリングされます。そして、コンポーネントがアンマウントされる際には、setData を使ってデータを空にしています。

以上が React のライフサイクルの基本的な理解と、それを Next.js と TypeScript を使って実装する一例です。この知識を元に、より複雑なアプリケーションのライフサイクルを理解し、効率的なコードを書くことができます。

Next.js と TypeScript で、React のライフサイクルの仕組みを理解する