Next.js と TypeScriptで、インポートとエクスポートを学ぶ

TwitterFacebookHatena

TL;DR

このページでは、Next.js と TypeScript の組み合わせで、モジュールの import/export の効果的な活用方法について解説します。これを通じて、コードの保守性と再利用性を向上させることが目的です。

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

named export と default export の違い

まずは、エクスポートから説明します。

JavaScript の基本的な概念である import/export は、ソースファイル間で変数や関数などを共有するための機能です。これにより、コードの再利用性と保守性を向上させることが可能となります。

英語では、named exportdefault export と呼ばれます。日本語では、それぞれ以下のように呼ばれます。

  • named export : 名前付きエクスポート
  • default export : デフォルトエクスポート

ただし、技術的な文脈では、英語の表現をそのまま使うことが多いです。

具体的には、以下のようなソースコードが一般的です。

// utils.js
export const sum = (a, b) => {
  return a + b
}

// main.js
import { sum } from './utils.js'

console.log(sum(1, 2)) // 3

named export と default export の違い

表にまとめると次のようになります。

named export default export
説明 モジュールが複数のエクスポートを持つ場合に便利。それぞれのエクスポートは独自の名前を持つ。 モジュールが一つの主要なエクスポートを持つ場合に便利。モジュールごとに一つだけ持つことができる。
使いどころ 共通のユーティリティ関数や、一緒に使われる関数群をエクスポートする場合など。 Next.js のページコンポーネントのエクスポートなど。主要なエクスポート(一つのコンポーネントや関数など)を他のモジュールからインポートする場合に便利。
インポート方法 import { namedExport } from 'module' のように波括弧 {} を使用してインポートする。 import defaultExport from 'module' のように、名前を付けてインポートする。または import * as name from 'module' のように全てのエクスポートをインポートすることも可能。
名前付け エクスポートする際に指定した名前をそのまま使用する必要がある。 インポートする際に任意の名前を付けることができる。
モジュールごとに何個でも作成できる。 モジュールごとに一つだけ持つことができる。

以上の表は、Next.js に限った話ではなく、一般的な JavaScript(または TypeScript)のモジュールシステムにおける named export と default export の使い所を説明しています。Next.js のページコンポーネントでは、ページを表現する一つのコンポーネントが主となるため、通常は default export を使用します。

それでは、JavaScript の import/export について、みかんとリンゴで例えて説明してみます。

まず、「named export(名前付きエクスポート)」とは、みかん箱から一つずつみかんを取り出すようなイメージです。箱の中にはたくさんのみかんが入っていますが、各みかんはそれぞれ名前がついていて、名前を指定して取り出すことができます。そして、一度に複数のみかんを取り出すこともできます。

JavaScript のコードで表すと以下のようになります。

// fruits.js
export const mikan = 'mikan'
export const apple = 'apple'

// main.js
import { mikan, apple } from './fruits.js'

一方、「default export(デフォルトエクスポート)」は、リンゴ一つをリンゴ箱から取り出すような感じです。リンゴ箱の中にはリンゴが一つだけ入っていて、箱を開けるとそのリンゴが手に入ります。そして、一つの箱からは一つのリンゴしか取り出せません。

JavaScript のコードで表すと以下のようになります。

// fruit.js
const apple = 'apple'
export default apple

// main.js
import apple from './fruit.js'

これが、named export と default export の違いです。違いを理解して、それぞれをうまく使い分けることが大切ですね。

インポート時の名前の付け方

名前付きエクスポート(named exports)とデフォルトエクスポート(default exports)では、インポート時の名前の付け方が異なります。

名前付きエクスポートはエクスポートされた変数の名前でインポートします。そのため、インポートする側では元の名前を使用する必要があります。ただし、as キーワードを使うことで、インポートする際に別の名前をつけることが可能です。

// original module
export const myFunction = () => {
  /* implementation */
}

// importing module
import { myFunction as renamedFunction } from './originalModule'

renamedFunction() // This works!

デフォルトエクスポートは、エクスポートする値に名前がないため、インポートする側で任意の名前をつけることができます。

// original module
export default () => {
  /* implementation */
}

// importing module
import anyNameYouLike from './originalModule'

anyNameYouLike() // This works!

上記のように、名前付きエクスポートでは基本的にエクスポート時の名前を保持しますが、asを使ってリネーム可能です。デフォルトエクスポートではインポート時に任意の名前をつけることができます。

インポートするときの波括弧 {} は何か?

JavaScript(および TypeScript)では、import { something } from 'module' の形式は名前付きエクスポート(named exports)から特定の機能や値をインポートするための記法です。ここで something はモジュールがエクスポートしている特定の機能や値の名前です。

波括弧 {} の中に名前を列挙することで、そのモジュールから複数の機能や値を一度にインポートすることが可能です。

例えば、次のようなモジュールがあるとします。

// myModule.ts
export const function1 = () => {
  /* implementation */
}
export const function2 = () => {
  /* implementation */
}

これを利用する場合、以下のようにインポートします:

// anotherModule.ts
import { function1, function2 } from './myModule'

次のコードの、import { css } の形式は、css という名前のエクスポートをそのモジュールからインポートすることを意味します。具体的には、Emotion(CSS-in-JS ライブラリ)の css 関数をインポートする際によく見かける記法です。

import { css } from '@emotion/react'

では、上のインポートを、import css from '@emotion/react' と書き換えて css 関数をインポートするとどうなるでしょうか?

この形式は、@emotion/react モジュールから default exportcss という名前でインポートするという意味になってしまいます。

しかし、@emotion/react モジュールは css 関数を default export として提供していません。css 関数は名前付きエクスポート(named export)として提供されています。したがって、css 関数をインポートするためには、import { css } from '@emotion/react' のように波括弧 {} を使って名前付きエクスポートをインポートする形式を使用する必要があります。

したがって、import css from '@emotion/react'; という記述はだめで、これを実行してしまうと css が未定義となるためエラーが発生します。

Next.js では default export を使う

Next.js では、主にページコンポーネントをエクスポートする際に default export が使われます。これにはいくつかの理由があります。

default export を使うと、そのファイルが主に何をエクスポートしているのかが一目でわかります。これは、特にページコンポーネントのような大きな機能単位のモジュールに対して有用です。例えば、HomePage コンポーネントが HomePage.tsx ファイルのデフォルトエクスポートになっていると、このファイルを開いたときにすぐにその目的が理解できます。

Next.js では、ファイルベースのルーティングが採用されています。つまり、pages ディレクトリ内の各ファイルはそれぞれが一つのルートとして扱われます。そのため、それぞれのファイルは一つの React コンポーネントをデフォルトでエクスポートする必要があります。

Next.js のプロジェクトでは、各ページのファイルで default export を使用することにより、コードベース全体での一貫性が保たれます。どのページファイルも同じ方法でエクスポートされているため、開発者は新しいページを作成するか、既存のページを修正する際に迷うことが少なくなります。

以上の理由から、Next.js ではページコンポーネントをエクスポートする際に default export が一般的に使われます。ただし、その他のヘルパー関数やコンポーネントなどをエクスポートする際には、名前付きエクスポート(named export)を使用することもあります。

Next.js での Import/Export の活用

それでは具体的に、Next.js と TypeScript での import/export の実装例を見ていきましょう。ここでは、Button コンポーネントを作成し、それを他のコンポーネントで再利用するという一般的なシナリオを想定します。

まず、components/Button.tsx ファイルを作成します。

// components/Button.tsx
import { MouseEventHandler } from 'react'

type ButtonProps = {
  onClick: MouseEventHandler<HTMLButtonElement>
  children: React.ReactNode
}

const Button = ({ onClick, children }: ButtonProps) => {
  return <button onClick={onClick}>{children}</button>
}

export default Button

次に、この Button コンポーネントを pages/index.tsx で利用します。

// pages/index.tsx
import Button from '../components/Button'

const IndexPage = () => {
  const handleClick = () => {
    console.log('Button clicked!')
  }

  return (
    <div>
      <h1>Welcome to Next.js!</h1>
      <Button onClick={handleClick}>Click Me!</Button>
    </div>
  )
}

export default IndexPage

このように、Next.js と TypeScript を組み合わせることで、再利用可能なコンポーネントを作成し、それを import して利用することができます。

Emotion での実装

さらに、Emotion を用いてスタイルを適用しましょう。Emotion は CSS-in-JS のライブラリの一つで、JavaScript の中で CSS スタイルを記述することができます。

以下の例では、Button コンポーネントにスタイルを追加します。

// components/Button.tsx
/** @jsxImportSource @emotion/react */
import { css } from '@emotion/react'
import { MouseEventHandler } from 'react'

type ButtonProps = {
  onClick: MouseEventHandler<HTMLButtonElement>
  children: React.ReactNode
}

const Button = ({ onClick, children }: ButtonProps) => {
  const buttonStyle = css`
    background-color: blue;
    color: white;
    border: none;
    padding: 10px 15px;
    border-radius: 5px;
    cursor: pointer;
    &:hover {
      background-color: darkblue;
    }
  `

  return (
    <button css={buttonStyle} onClick={onClick}>
      {children}
    </button>
  )
}

export default Button

このように、Emotion を使用してスタイルを適用すると、コードの見た目と機能を一箇所に集約することができます。

以上が、Next.js と TypeScript を使った import/export の活用方法の説明でした。これを通じて、再利用性と保守性が向上し、より効率的な開発が可能になりますよね。

Next.js と TypeScriptで、インポートとエクスポートを学ぶ