Reactおじさんブログ

ReactでDefault exportとNamed exportどちらを使用すれば良いのか??

日付更新日サムネイル

どうもReactおじさんです。

今回はJavaScript/TypeScriptで使用するDefault exportとNamed exportの違いと使い分けをReactベースでまとめてみました。

あくまでも私が調べた中での結果なので、その辺を理解した上で読んでいただけると幸いです。

さっそく両者の違いについて確認していきましょう。

Default export

/* export */
const Button = () => {
  return <button>テスト</button>;
};

export default Button;

---------------------------------------------------------------

/* import */
 // 命名する
import Button from '@/components/Button'; 


importする側で対象を命名する必要がある

上記の例で参考にすると、Buttonを自分で命名する必要があります。

これはButtonコンポーネントに対して、Buttonと命名し何をimportしているのか明確にわかるようになっていますが、下記の場合どうでしょう。

/* import */
import ScrollButton from '@/components/Button';
import SubmitButton from '@/components/Button';

対象を自分で命名できるのは便利ではあります。

しかし、同じコンポーネントを使用しているのにあるページではScrollButton、あるページではSubmitButtonとなっているとコンポーネント名が統一されず、Buttonコンポーネント自体がどのようなものか不明確になり良くありません。

また、moduleの命名を変更し、import先で修正ミスがあった場合、動かしてみるまでエラーが見つからないことは欠点です。

Default exportを使用するなら命名はプロジェクトでしっかり管理することが重要になってくるでしょう。

React.lazyを使用する時はDefault exportしか使えない

ここではReact.lazyについてあまり触れませんが、簡潔に説明するとファイルを分割して遅延読み込みができる機能です。

こんな感じ↓

const ButtonComponent = lazy(() => import('../components/Button'));

ButtonコンポーネントをNamed exportにするとエラーが発生します。

公式を確認してみた所、Named exportには対応していないようです。

気になる方は公式を確認してみてください。
公式: React.lazy

Named export

/* export */
export const Button = () => {
  return <button>テスト</button>;
};

---------------------------------------------------------------

/* import */
import { Button } from '@/components/Button'


import、exportで命名が統一できる

export側で命名を変更した際に、import側でエラーが出る。

/* export */
// TestButtonに変更
export const TestButton = () => {
  return <button>テスト</button>;
};

---------------------------------------------------------------

/* import */
import { Button } from '@/components/TestButton'
// errorが出て正常にimportできない


Default exportだとエラーが出ないので動かしてみるまで変更点に気づきにくい部分がありますが、Named exportだと変更したタイミングでエディタ上にエラーが表示され、バグを未然に防止できます。(VSCodeを想定)

また、importする際に補完が効くのでタイポ防止にもなります。

TypeScriptを使用している場合、import側で型定義できる

/* Type Ailias export */
export type ApiResponse = {
  name: string;
  age: number;
};

/* interface export */
export interface ApiResponse {
  name: string;
  age: number;
};

---------------------------------------------------------------

/* import */
import { ApiResponse } from './types'  // Named import
import type { ApiResponse } from './types'  // Type-Only Imports
import  { type ApiResponse } from './types'  // Type Modifiers on Import Names 


型をimportする際にいくつか方法があるので順番に見ていきましょう。

■Named import
Named exportした際にimportしてくる構文になります。
そのまま型を付与すればOKです。

■Type-Only Imports and Export
こちらはTypeScript3.8から追加された機能で、moduleから型をimportする際に、typeを使用して型のみをimportできる構文です。

しかし、この構文には制限があり値のimportと同時に使用することはできず、一つのモジュールから型情報と値どちらもimportする場合にはそれぞれimportを別に書かなくてはいけません。

こんな感じ↓

/* export */
export type Props = {
   children: string
}

export const Button = ({ children }) => {
  return <button>{children}</button>;
};

---------------------------------------------------------------

/* import */
import { Button } from '@/components/Button' // コンポーネントをimport
import type { Props } from '@/components/Button' // 型をimport


■type Modifiers on Import Names 
この機能はTypeScript4.5で追加された機能で、今回の記事を執筆している時に初めて知りましたw

簡単に説明すると、Type-Only Imports and Exportで値と型のimportが同時に出来なかったものができるようになりました。

こんな感じ↓

/* export */
export type Props = {
   children: string
}

export const Button = ({ children }) => {
  return <button>{children}</button>;
};

---------------------------------------------------------------

/* import */
import { Button, type Props } from '@/components/Button' 


この利点は同じmoduleから値と型の情報がimportできるようになるので、Type-Only Imports and Exportで値と型を分けていた部分が解消されました。

まとめ

Default exportとNamed exportについてまとめてきましたが、Next.jsなどDefault exportを使用しないといけない箇所以外はNamed exportを中心に使っていく方が良いと判断しました。

やはり命名でバグを未然に防ぐことができるのが開発を進めていく上で生産性が高くなるので一番の利点だと思っています。
他にもTypeScriptの機能が豊富なのが魅力です。

この辺は宗教的な話になるので正解はありませんが、もっと良い意見や考え方があればTwitterでDMください。