Yanonoblog!

こつこつと

import / export 文の使い方や慣習について調べてみる

はじめに

普段のフロント開発で用いる"import""export"の基本を復習したいと思ったのと、書く際に相対パスよりも絶対パスでは本来どちらが良いのか?記述する上で順番などの慣習はあるのか?など疑問に感じることがあったためまとめてみようと思いました。

モジュール化

モジュール化とはJavaScriptに限らず他の言語でも昔からある概念で、大きなソフトウェアやアプリケーションを小さな単位に分割し、各単位を独立したモジュールとして開発する手法のことを指します。

以下はユーザーの名前と年齢を表示するリストを作成するコンポーネントを定義し、export defaultコンポーネントをexportしモジュール化しています。

const UserList: React.FC<UserListProps> = ({ users }) => {
  return (
    <div>
      <h1>User List</h1>
      <ul>
        {users.map((user) => (
          <li key={user.id}>
            {user.name}, {user.age}
          </li>
        ))}
      </ul>
    </div>
  );
};

export default UserList; // exportすることで定義した内容を他のモジュール内でimportすることが出来る

上記のようにexportすることで以下のように別のファイルからでもimportして再利用することが可能です。

import React from 'react';
import UserList from './UserList'; // import文を書くことで使い回すことができる

interface User {
  id: number;
  name: string;
  age: number;
}

const users: User[] = [
  { id: 1, name: 'Alice', age: 25 },
  { id: 2, name: 'Bob', age: 30 },
  { id: 3, name: 'Charlie', age: 35 },
];

const App: React.FC = () => {
  return (
    <div>
      <UserList users={users} /> // UserListコンポーネントを使うことが出来る
    </div>
  );
};

export default App;

モジュール化のメリット

名前空間の衝突を避けるための分離

大規模なアプリケーションでは、複数のJavaScriptファイルを読み込むことが必要になります。

しかし、複数のファイルで同じ変数名や関数名を使用してしまうと、名前空間の衝突が発生してしまいます。

モジュール化を行うことで、各モジュール内で定義された変数や関数が外部のコードと干渉しないようにし、名前空間の衝突を避けることができます。

再利用性と保守性の向上

モジュール化により、機能ごとに分割された小さなモジュールを作成することができます。

これによりコードの再利用性が高まり、保守性が向上します。また、依存関係が明確になるため、モジュールごとに開発・テストが行いやすくなります。

さらに、小さなモジュール単位での開発が可能になるため、大規模なアプリケーションの開発が容易になります。

基礎

名前付きエクスポート / インポート

関数や変数を定義する際に先頭にexportを記述することで名前付きエクスポートすることができます。

const foo = "foo";

export { foo }; // 宣言済みのオブジェクトを名前つきエクスポートする
export function bar() {  // 関数もexport文で名前付きエクスポートすることができる
  return "bar";
}

上記のようにすると以下のようにexportしたファイルのパスを指定して名前付きインポートし、他のモジュールで定義した変数が使えるようになります。

import { foo, bar } from './example.js'; // 分割代入で指定する際はカンマ区切りにすることで複数インポートも可能

console.log(foo); // => "foo"
console.log(bar()); // => "bar"

デフォルトエクスポート/インポート

デフォルトエクスポートでは各モジュールの中で最も重要なエクスポートを1つだけ指定することができます。

単純なコンポーネントを作成&デフォルトエクスポートしています。

import React from "react";

const HelloWorld = () => {
  return (
    <div>
      <h1>Hello, World!</h1>
    </div>
  );
};

export default HelloWorld;

上記のようにすると以下のようにAppコンポーネント内でHelloWorldコンポーネントを呼び出すことができます。

import React from "react";
import HelloWorld from "./HelloWorld";

const App = () => {
  return (
    <div>
      <HelloWorld />
    </div>
  );
};

export default App;

慣習について

基本的には1ファイル ⇔ 1モジュール

JavaScriptにおいて、1ファイル ⇔ 1モジュールとすることが一般的です。

ファイル内で定義された関数や変数は、そのファイルのスコープ内でのみ有効になるため、ファイル間での名前の衝突を回避コードの保守性を向上させることができます。

ファイルの機能が明確になるため、コードの可読性が向上し、開発効率も高くなります。

大規模なプロジェクトの場合は1つのファイルで実装するのが難しいケースがありますが、その場合は、複数のファイルを1つのモジュールとしてまとめることもあります。ただし、できるだけ1つのモジュールが独立した機能を持つようにすることが好ましいです。

デフォルトエクスポートを使うメリット

  1. 名前を指定せずにインポートできるので、簡潔なコードになる
  2. エクスポートする値が1つだけなので、モジュールの意図が明確になり、使い方がシンプルになる
  3. 名前がないため、再利用性の高いコンポーネントなどをエクスポートするときに、インポート先で自由に名前をつけられるため、コードを簡潔に保ちながらも柔軟性が高まる
export const MyComponent = (props) => {
  return <div>{props.text}</div>;
};

// デフォルトエクスポートの場合は自由に名前をつけられる。
import SomeName from "./my-module.js";

名前付きエクスポートを使うメリット

  1. 複数のエクスポートを許可するため、必要な値だけを選択してインポートすることができる
  2. 名前があるため、コード内でエクスポートされた値の意図が明確になり、可読性が高くなる
  3. 関数やクラスなど、個々のエクスポートに名前をつけることができるため、インポート先で意図が明確になり、コードの保守性が向上する
export const MyComponent = (props) => {
  return <div>{props.text}</div>;
};

// 名前付きエクスポートの場合は、必ずインポートするときに指定した名前で呼び出さなければな
import { MyComponent } from "./my-module.js";

デフォルトエクスポートはシンプルで簡潔なコードを実現し、名前付きエクスポートは柔軟性と可読性を向上させるために使用されます。

ES6ではデフォルトエクスポートが推奨されている

名前付きエクスポートは、複数のエクスポートを許可し、それぞれに名前を付けてエクスポートします。

import { foo } from "./my-module.js";

一方で推奨されているデフォルトエクスポートでは、1つのモジュールから1つのエクスポートのみを許可するため、エクスポートに名前を付けずに直接エクスポート&インポートすることができます。

import foo from "./my-module.js";

相対パスでimport / export文を書くメリット

  1. プロジェクト内のファイルへのアクセスが、ファイル構成に合わせて柔軟にできます。
  2. 相対パスを使用することで、依存関係が直感的に理解でき、修正や移動がしやすくなります。

絶対パスでimport / export文を書くメリット

1. ファイルの移動やファイル名の変更に強い 絶対パスを使用することで、ファイルの移動やファイル名の変更に影響を受けずにコードを書くことができます。

相対パスを使っている場合、ファイルを移動したり、ファイル名を変更したりすると、コードを修正する必要になる場合があります。

2. コードの可読性が高い 絶対パスを使用することで、どのファイルがインポートされているかが一目でわかります。

相対パスを使用する場合、どのディレクトリから相対的にパスを記述しているかを開発者が理解する必要があります。

3. IDEの支援が受けられる 絶対パスを使用すると、IDEが自動補完やコードナビゲーションの支援を行いやすくなります。

IDEによっては、相対パスでのインポートでは補完やナビゲーションがうまく動作しないことがあります。

絶対パスの方が推奨されている

相対パスも一般的ではありますが上述したメリットが大きいため絶対パスの方がわかりやすいようです。 無理に方針を途中で変更する必要はないと思います。

絶対パスはパスが長くなり、記述するのが面倒になることがあります。

また、プロジェクトのルートパスからのパスを書く必要があるため、ディレクトリの階層が深い場合には、パスの記述が複雑になってしまうことがあります。

後述しますが、上記の問題は解決できます。

階層が深くなった場合に有効な記述・設定方法

階層が深くなった場合の例です。

├── src
│   ├── components
│   │   └── pages
│   │       ├── Layout.tsx

src 配下の別階層から Layout component を参照した場合、相対パスの場合は以下のようになります。

個人的にこの改修を繰り返しているうちに「../」の数が連続しているとこんがらがってきます。(笑)

import Layout from '../../../../components/templates/Layout';

絶対パスでimport の階層が深くなった場合、baseURLを定義しておくことで以下のようにスッキリ書くことができます。

import Layout from 'components/pages/Layout';

tsconfig.json の compilerOptions に baseUrl を追記します。

あとは include に 対象のファイルを記載するだけです。

{
  "compilerOptions": {
    "allowJs": true,
           :
           :
    "baseUrl": "src" // 追記
  },
  "exclude": ["node_modules"],
  "include": ["src/**/*.ts", "src/**/*.tsx"] // 対象となるファイル

インポート順

インポート文もバラバラに追記していくのではなくプロジェクトによって規則を決めてやると良さそうです。 VScodeの機能で並び替えたりすることも可能です ESlintで自動整形を行ったりすることもできますのでプラグインを使用するのがベストかなと個人的に思います。 こちらの記事などが参考になります! https://zenn.dev/riemonyamada/articles/02e8c172e1eeb1

おわりに

コメント

本記事の内容は以上になります!

基礎中の基礎の内容でしたが、慣習など疑問に思ったことも含めて理解が出来ると良いと思いました。少しでも参考になったり学びのきっかけになりますと幸いです。

間違いがありましたら修正いたしますので、ご指摘ください。

興味があれば他の記事も更新していきますので是非ご覧になってください♪


◇ プログラミングスクールのご紹介 (卒業生より)

https://runteq.jp/r/ohtFwbjW

ご不明な点ありましたらお気軽にコメントどうぞ!

参考