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 export
と default 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 export
を css
という名前でインポートするという意味になってしまいます。
しかし、@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 の活用方法の説明でした。これを通じて、再利用性と保守性が向上し、より効率的な開発が可能になりますよね。