Next.js と TypeScriptで学ぶ : 再帰関数は「ドミノ倒し」

TwitterFacebookHatena

TL;DR

この記事では、再帰関数の実装方法とそれを Next.js と TypeScript でどう活用するかについて解説します。特に、コンポーネントの再帰的なレンダリングを使った例を紹介します。

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

関数に関する記事

再帰関数とは?

再帰関数とは、関数の中から自分自身を呼び出す関数のことを言います。プログラミングにおける再帰は、繰り返し処理を行う一つの方法で、特定の条件が満たされるまで関数が自身を呼び出し続けます。

なんだか難しそうな単語が出てきたかもしれませんが、実はシンプルなアイデアに基づいています。

再帰関数は「ドミノ倒し」

再帰関数を理解するためには、「ドミノ倒し」をイメージしてみてください。ドミノ倒しは、一つのドミノを倒すとその次のドミノも倒れて、その次のドミノも倒れて...というように、一つ一つのドミノが次のドミノを倒していく遊びですよね。

これを再帰関数に置き換えて考えてみると、一つの関数(ドミノ)が次の同じ関数(ドミノ)を呼び出し(倒し)、その関数がまた次の同じ関数を呼び出し(倒し)...というように、一つ一つの関数が次の関数を呼び出していくイメージです。

まさに、「一つ倒すと、全てが動き出す」というわけです!

ただし、ドミノ倒しのように無限に続いてしまうと問題があるので、ある条件が満たされたら(全てのドミノが倒れたら)再帰は終了し、関数の呼び出しはストップします。この「止まる条件」をプログラミングでは「ベースケース」や「終了条件」などと呼びます。

例えば、フィボナッチ数列を生成する再帰関数は、数列の次の数を求めるために自分自身を二回呼び出します(二つ前の数と一つ前の数を足し合わせるため)。しかし、「1 以下の数が来たらそのまま返す」という終了条件を設けることで、無限に再帰することなく適切な値を計算することができます。

それぞれの関数(ドミノ)は自分が何番目かは気にせず、ただ自分の「仕事」をするだけなんです。これが再帰関数の面白いところで、大きな問題を小さな問題に分けて解く力があります。

このように、再帰関数は「自分自身を呼び出す関数」であり、その仕組みはドミノ倒しみたいな感じなんだということを覚えておいてくださいね。

どういったときに使われるのかというと、再帰関数は問題をより小さな問題に分解し、それを解決するために使用されます。これは一般的に、問題が自己類似的な構造を持つ場合に特に有効です。例えば、フラクタルの描画、ファイルシステムの探索、あるいはデータ構造(特に木構造)の探索などに再帰がよく使用されます。また、数学的な問題(フィボナッチ数列の計算など)でもよく用いられます。

以下はシンプルな再帰関数の一例です。

const countDown = (num: number): void => {
  console.log(num)
  if (num > 0) {
    countDown(num - 1)
  }
}

console.log(countDown(10))
// 10
// 9
// ...
// 0
// undefined

Next.js で、再帰関数を活用

Next.js と TypeScript を使用して、再帰関数を活用する一つの例として、コンポーネントの再帰的なレンダリングを見ていきましょう。

ファイル名: /components/RecursiveList.tsx

import React from 'react'

type ListData = {
  id: number
  name: string
  children?: ListData[]
}

type RecursiveListProps = {
  listData: ListData[]
}

const RecursiveList: React.VFC<RecursiveListProps> = ({ listData }) => {
  return (
    <ul>
      {listData.map((data) => (
        <li key={data.id}>
          {data.name}
          {data.children && <RecursiveList listData={data.children} />}
        </li>
      ))}
    </ul>
  )
}

export default RecursiveList

このコンポーネントは、再帰的なデータ構造(この場合は木構造)を表現できます。例えば、Web サイトのナビゲーションメニューのような階層的なデータを表示するときに非常に役立ちますよね。

フィボナッチ数列を実装

再帰関数をさらに深く理解するために、おなじみのフィボナッチ数列を計算する再帰関数を作成しましょう。

const fibonacci = (n: number): number => {
  if (n <= 1) {
    return n
  } else {
    return fibonacci(n - 1) + fibonacci(n - 2)
  }
}

console.log(fibonacci(10)) // 55

ここで、fibonacci 関数は、n が 1 以下の場合には n を返し、それ以外の場合には fibonacci(n - 1) + fibonacci(n - 2) を返します。これはフィボナッチ数列の定義そのものですね。

次に、ディレクトリ構造を表すデータを扱うための再帰関数の例をご紹介します。

この例では、各ディレクトリがサブディレクトリのリストを持つようなツリー構造のデータを扱います。再帰関数はこのようなツリー構造のデータを処理するのに非常に適しています。型注釈を Props として指定しています。

ファイル名: /components/RecursiveList.tsx

type ListData = {
  id: number
  name: string
  children?: ListData[]
}

type RecursiveListProps = {
  listData: ListData[]
}

const RecursiveList = ({ listData }: RecursiveListProps) => {
  return (
    <ul>
      {listData.map((item) => (
        <li key={item.id}>
          {item.name}
          {item.children && <RecursiveList listData={item.children} />}
        </li>
      ))}
    </ul>
  )
}

export default RecursiveList

この RecursiveList コンポーネントは、listData プロパティとして受け取った配列を基にリストをレンダリングします。各要素がさらに子要素を持つ場合(children プロパティが存在する場合)、それらの子要素に対しても同様に RecursiveList コンポーネントを使用してリストを作成します。このように自分自身を呼び出すことで、ツリー構造のデータを自然に表現することができます。

Emotion で実装

Emotion を使用して、再帰的にレンダリングされるコンポーネントにスタイルを適用してみましょう。

ファイル名: /components/StyledRecursiveList.tsx

/** @jsxImportSource @emotion/react */
import { css } from '@emotion/react'
import RecursiveList from './RecursiveList'

type ListData = {
  id: number
  name: string
  children?: ListData[]
}

type StyledRecursiveListProps = {
  listData: ListData[]
}

const StyledRecursiveList = ({ listData }: StyledRecursiveListProps) => {
  return (
    <div css={container}>
      <RecursiveList listData={listData} />
    </div>
  )
}

export default StyledRecursiveList

const container = css`
  font-family: Arial, sans-serif;
  ul {
    padding-left: 20px;
    border-left: 1px solid #ccc;
    li {
      margin-bottom: 10px;
    }
  }
`

ここで、Emotion を使って再帰的にレンダリングされるリストのコンテナにスタイルを追加しています。また、ulli タグにもスタイルを適用して、リストが階層化されていることを視覚的に強調しています。

再帰関数は単純なアイデアに基づいていますが、そのパワーは絶大です。この記事を通じて、Next.js と TypeScript で再帰関数を活用する一歩を踏み出せたら嬉しいです。

Next.js と TypeScriptで学ぶ : 再帰関数は「ドミノ倒し」