転職を繰り返したサラリーマンの多趣味ブログ

30才未経験でSEに転職した人の多趣味ブログ

TypeScriptのtypeとinterfaceの違いを整理してみた【ユニオン型も解説】

こんにちは。今日は TypeScript の typeinterface の違い、そしてよく一緒に出てくる ユニオン型 についてまとめます。
自分の勉強メモも兼ねて書いているので、これから TypeScript を学ぶ人の参考になれば嬉しいです。

typeinterfaceの共通点
  • どちらも 型を定義するために使える
  • オブジェクトの形を表したり、再利用性を高めることができる

一見すると「どっちを使っても同じじゃない?」と思うかもしれませんが、実は特徴が少し違います。

typeの特徴
  • ユニオン型やプリミティブ型のエイリアスに使える
  • &(インターセクション)を使えば型を合成できる
  • ただし 宣言マージはできない

例:ユニオン型の定義

export type AccountingType = 
  | "収入" 
  | "支出(現金)" 
  | "支出(クレジットカード)";

この場合、AccountingType は 3つの文字列のどれか を意味します。

interfaceの特徴
  • 主にオブジェクトの形を表現するのに使う
  • extends で他のインターフェースを継承できる
  • 宣言マージが可能 → 後から同じ名前で再定義すると自動で結合される

例:オブジェクト型の定義

export interface Accounting {
  id: string;
  date: string;
  amount: number;
  content: string;
  type: AccountingType;
  category: IncomeCategory | ExpenseCategory;
}
宣言マージの違い

interface の場合

同じ名前で再宣言すると自動で結合されます。

interface Accounting {
  id: string;
  amount: number;
}

// 後から拡張できる!
interface Accounting {
  memo: string;
}

const a: Accounting = {
  id: "1",
  amount: 1000,
  memo: "昼ごはん代"
};

あとから自由にフィールドを追加できるのが大きな特徴。

type の場合

同じ名前で再宣言はできません。

type Accounting = {
  id: string;
  amount: number;
};

// 再宣言するとエラー
// type Accounting = { memo: string };

// 拡張するなら & を使う
type AccountingWithMemo = Accounting & { memo: string };

const b: AccountingWithMemo = {
  id: "2",
  amount: 2000,
  memo: "家賃"
};

type は その場で閉じた型を作るイメージです。

ユニオン型とは?

ユニオン型は「どれかひとつ」を表す型です。
| を使います。

let value: string | number;

value = "こんにちは"; // OK
value = 123;         // OK
value = true;        // エラー

今回の例だと、IncomeCategory | ExpenseCategory がまさにユニオン型です。
「収入カテゴリ」か「支出カテゴリ」のどちらか、という意味になります。

まとめ

type

  • ユニオン型やプリミティブ型に強い
  • 拡張するときは & を使う

interface

  • オブジェクトの型を表すのに便利
  • 宣言マージがあるので後から拡張できる

ユニオン型

  • 「型の選択肢を列挙」できる便利な仕組み

僕の場合、

  • 固定的に使う型 → type
  • 将来拡張したい型 → interface

という感じで使い分けています。
TypeScript の型は慣れると本当に強力なので、ぜひいろいろ試してみてください。

React × TypeScript × FullCalendar 開発での注意点

先日、React × TypeScript × FullCalendar を使って開発していたときに、以下のようなエラーに遭遇しました。

'DatesSetArg' は型であり、'verbatimModuleSyntax' が有効であるときは、
型のみのインポートを使用してインポートされる必要があります。ts(1484)

最初は「何これ?」と戸惑ったのですが、調べてみると TypeScript 5.0 以降の仕様変更 が関係していました。今回はこのエラーの概要、原因、そして対策方法をまとめます。

エラーが出たコード例

FullCalendar の DatesSetArg 型をインポートして使おうとしたときにエラーが発生しました。

import dayGridPlugin from "@fullcalendar/daygrid";
import FullCalendar from "@fullcalendar/react";
import { DatesSetArg } from '@fullcalendar/core' // ←ここでエラー

const Calendar = () => {
  const handleDateSet = (datesetInfo: DatesSetArg) => {
    const currentMonth = datesetInfo.view.currentStart
  }
}
エラーの原因

TypeScript 5.0 から導入された verbatimModuleSyntax がポイントです。
これを true にしていると、

を明確に分けてインポートする必要があります。
今回 DatesSetArg型だけ なので、普通の import を使うとエラーになってしまいました。

解決方法

解決方法は大きく2つあります。

1. import type を使う

型だけをインポートする場合は import type を使えばOKです。

import type { DatesSetArg } from '@fullcalendar/core'

これでエラーは解消されますし、「これは型だよ」というのが明確になるので、将来的にもおすすめの方法です。



2. tsconfig.jsonverbatimModuleSyntax を無効にする

もし「いちいち import type を書くのが面倒」という場合は、tsconfig.json で設定をオフにする方法もあります。

{
  "compilerOptions": {
    "verbatimModuleSyntax": false
  }
}

これで従来通りの挙動に戻せます。

まとめ
  • TypeScript 5.0 から 型と値のインポートを区別 する必要がある
  • DatesSetArg は「型」なので、普通の import だとエラーになる
  • 解決方法は
  1. import type を使う
  2. tsconfig.jsonverbatimModuleSyntax: false にする

自分は今回、素直に import type に修正して解決しました。
同じように TypeScript 5.0 以降で「急にエラーが出た!」と困っている方の参考になれば幸いです!

createThemeでデザインルールを一元管理する

フロントエンド開発では、見た目を整えるためにCSSやUIフレームワークを組み合わせて使うことが多いですが、その中でも人気が高いのが MUI (Material UI) です。
MUIは、Googleが提唱する Material Design に基づいたコンポーネントを提供してくれるライブラリで、Reactとの相性も非常に良いのが特徴です。

この記事では、MUIのインストールからテーマ機能である createTheme の役割までを解説します。

MUIのインストール

まずはプロジェクトにMUIを追加します。
npm install @mui/material @emotion/react @emotion/styled

  • @mui/material: MUIの本体パッケージ
  • @emotion/react / @emotion/styled: MUIが内部で利用しているCSS-in-JSライブラリ

これらをインストールすることで、MUIのコンポーネントをReactで利用できるようになります。

MUIのコンポーネントを使ってみる

インストール後、まずはボタンを表示してみましょう。

import Button from '@mui/material/Button';

function App() {
  return <Button variant="contained">ADD EVENT</Button>;
}


このコードを実行すると、MUI標準のスタイルが適用されたボタンが表示されます。
CSSを一切書かずに、洗練されたUIを利用できるのがMUIの大きな利点です。

createThemeとは?

MUIを使っていると必ず出てくるのが 「テーマ (theme)」 という概念です。

テーマの役割

といった、アプリ全体のデザインルールをひとまとめに管理できます。
このテーマを生成するのが createTheme です。

createThemeの基本例
import { createTheme, ThemeProvider } from '@mui/material/styles';
import { CssBaseline, Button } from '@mui/material';

const theme = createTheme({
  palette: {
    primary: {
      main: '#1976d2', // プライマリカラー
    },
    secondary: {
      main: '#9c27b0', // セカンダリカラー
    },
  },
  typography: {
    fontFamily: 'Roboto, Arial, sans-serif',
    fontSize: 14,
  },
});

function App() {
  return (
    <ThemeProvider theme={theme}>
      <CssBaseline />
      <Button variant="contained" color="primary">プライマリ</Button>
      <Button variant="outlined" color="secondary">セカンダリ</Button>
    </ThemeProvider>
  );
}


ポイント

  • createTheme(...) でテーマオブジェクトを作成
  • ThemeProvider でアプリ全体をラップすると、そのテーマが全てのMUIコンポーネントに適用
  • CssBaseline を追加すると、ブラウザごとのデフォルトCSSをリセットし、統一的な見た目を実現
さらにカスタマイズ

MUIのテーマは、単に色やフォントを設定するだけでなく、独自のプロパティを追加して拡張 することもできます。
例えば「未着手」「進行中」「完了」といった、アプリ固有の色をテーマに組み込んでおけば、アプリ全体で一貫したカラールールを適用できます。

MUIでは @mui/material/styles を拡張して型定義を追加することで、createThemepalette に独自のキーを生やせます。

// MUIのテーマ拡張を行うための型定義
declare module "@mui/material/styles" {
  // theme.palette に独自の色を追加
  interface Palette {
    todoColor: PaletteColor;
    inProgressColor: PaletteColor;
    doneColor: PaletteColor;
  }

  // createTheme の引数に渡す型も拡張
  interface PaletteOptions {
    todoColor?: PaletteColorOptions;
    inProgressColor?: PaletteColorOptions;
    doneColor?: PaletteColorOptions;
  }
}

export const theme = createTheme({
  typography: {
    fontFamily: 'Noto Sans JP',
    fontWeightRegular: 400,
    fontWeightMedium: 500,
    fontWeightBold: 700,
  },

  palette: {
    // 未着手の色
    todoColor: {
      main: blue[500],
      light: blue[100],
      dark: blue[700],
    },
    // 進行中の色
    inProgressColor: {
      main: red[500],
      light: red[100],
      dark: red[700],
    },
    // 完了の色
    doneColor: {
      main: green[500],
      light: green[100],
      dark: green[700],
    },
  },
});

ポイント

  • declare module "@mui/material/styles" を使って Palette / PaletteOptions を拡張すると、TypeScriptで型補完が効くようになる
  • theme.palette.todoColor.main のようにアクセス可能になり、アプリ全体で一貫したカラールールを適用できる
  • ドメインに合わせて「アプリ固有の色」をテーマに登録することで、スタイルの管理がシンプルになる

レスポンシブ対応
テーマに含まれる breakpoints を使うと、画面サイズごとにスタイルを切り替え可能です。

sx={{
  fontSize: {
    xs: '0.8rem', // モバイル
    sm: '1rem',   // タブレット以上
    md: '1.2rem', // デスクトップ以上
  }
}}
まとめ
  • MUIを導入すると、Material Designに沿ったUIをすぐに使える
  • createTheme は「アプリ全体のデザインルール」をまとめる仕組み
  • ThemeProvider でラップすれば、全コンポーネントに統一的なスタイルが反映される
  • 色・文字・余白・レスポンシブ・コンポーネントごとのデフォルトを一括で管理可能

デザインを手作業で整えるのは大変ですが、MUIと createTheme を使えば 「開発スピード」と「統一感のあるUI」 を両立できます。

TypeScriptエラー対策:MUI v7 Grid APIの変更に注意

MUIを使って以下のようなコードを書いたところ、TypeScriptでエラーが出ました。

import { Card, CardContent, Grid, Typography } from "@mui/material";

const MonthlySummary = () => {
  return (
    <Grid container spacing={{ xs: 1, sm: 2 }} mb={2}>
      <Grid xs={4} display="flex" flexDirection="column">
        <Card sx={{ color: "white", borderRadius: "10px", flexGrow: 1 }}>
          <CardContent>
            <Typography variant="h6">Summary Title</Typography>
            <Typography variant="body2">Summary content goes here.</Typography>
          </CardContent>
        </Card>
      </Grid>
    </Grid>
  );
};

export default MonthlySummary;

出てきたエラーメッセージはこちらです。

この呼び出しに一致するオーバーロードはありません。
  ...
    プロパティ 'component' は型 '{ children: Element; xs: number; display: "flex"; flexDirection: "column"; }' にありませんが、
    型 '{ component: ElementType<any, keyof IntrinsicElements>; }' では必須です。
  ...
    プロパティ 'xs' は型に存在しません。

つまり、xs が存在しないとか component が必須と怒られてしまいました。

原因

調べてみると、これは MUI v7 で Grid のAPIが変更されたことが原因でした。

旧Grid(v5まで / v6 Legacy)

  • 子要素に item を書く必要がある
  • サイズ指定は xs={4} などで行う

新Grid(v7以降)

  • 子要素は常に item 扱い → item は不要
  • サイズ指定は size={{ xs: 4 }} または size={4} に変更

今回、自分は import { Grid } from "@mui/material"; で 新しいGrid を読み込んでいるのに、書き方を旧Gridのままにしていたため、型エラーが出ていました。

解決方法

エラーを直す方法は2通りあります。

1. 新しいGridに合わせる

<Grid container spacing={{ xs: 1, sm: 2 }} sx={{ mb: 2 }}>
  <Grid size={{ xs: 4 }} display="flex" flexDirection="column">
    ...
  </Grid>
</Grid>

ポイント

  • 子は item を書かない
  • xs ではなく size を使う

2. 旧Grid(GridLegacy)を使う

もしこれまで通り xs={4}item を使いたいなら、GridLegacy をインポートします。

import Grid from "@mui/material/GridLegacy";

<Grid container spacing={2} sx={{ mb: 2 }}>
  <Grid item xs={4}>
    ...
  </Grid>
</Grid>
まとめ
  • MUI v7 から Grid のAPIが刷新され、xs → size に変更された
  • APIを使いたければ GridLegacy をインポートする
  • 新しいAPIに寄せるのがおすすめ



自分は最初「component が必須?」「xs が存在しない?」と混乱しましたが、調べてみたら単純にGridの世代が違うだけでした。
同じようにハマった人の参考になれば幸いです!

Reactコンポーネントの書き方とexport方法の違い

Reactコンポーネントの書き方にはいくつかパターンがあります。
特に 関数の書き方 と export方法(default / named) の違いは、最初は分かりにくいところです。

この記事では、

  • default export(後出し)の意味と使いどころ
  • 巻き上げ(hoisting)とは何か
  • 関数の書き方+exportの違いと使い分け

をわかりやすくまとめます。


1. export の種類

default export(デフォルトエクスポート)

  • 1ファイルにつき1つだけ
  • import時に好きな名前を付けられる
export default function App() {
  return <div>App</div>
}

// import例
import MyApp from './App'

named export(名前付きエクスポート)

  • 1ファイルにいくつでも書ける
  • import時は名前を合わせる必要がある
export const Home = () => <h1>Home</h1>
export const About = () => <h1>About</h1>

// import例
import { Home, About } from './pages/Home'


2. 関数の書き方の2種類

関数宣言(function declaration)

function App() {
  return <div>App</div>
}

巻き上げ(hoisting) がされる
→ ファイル内で宣言より前に呼び出しても使える

関数式(アロー関数)

const Home = () => {
  return <div>Home</div>
}

巻き上げされない
→ 宣言より前で呼び出すとエラーになる


3. 巻き上げ(hoisting)とは?

JavaScriptでは、関数宣言や変数宣言が実行前にファイルの先頭に持ち上がったように扱われる仕組みがあります。

例(関数宣言はOK):

sayHello() // ✅ 動く

function sayHello() {
  console.log("Hello")
}

例(アロー関数はNG):

sayHello() // ❌ エラー

const sayHello = () => {
  console.log("Hello")
}


4. default export 後出し とは?

関数を先に宣言し、最後に export default する書き方です。

// 先に変数や関数などの準備
const appTitle = "My Cool App"

function App() {
  return <h1>{appTitle}</h1>
}

// 最後にdefault export
export default App

この書き方のメリット:

  • コンポーネントの前に変数や補助関数を置ける
  • 同じファイル内で複数の関数を定義し、最後に主役だけexportできる


5. まとめ

  • default export … 主役コンポーネント向き。import時に名前を自由に決められる
  • named export … 複数コンポーネントや関数をまとめて管理するのに便利
  • 関数宣言 は巻き上げされるが、アロー関数はされない
  • default export後出し は、処理や補助関数を前に書いて最後に主役だけexportしたいときに使う

ReactでSPA構築: react-router-domの基本的な使い方

ReactでSPA(シングルページアプリケーション)を作るとき、ほぼ必須になるのがreact-router-domです。
今回は基本的な使い方から、AppLayoutを使った共通レイアウトの作成方法までまとめていきます。


1. react-router-domとは?

react-router-domReact用のルーティングライブラリ です。

「ルーティング」とは、URLに応じてどのコンポーネントを表示するかを切り替える仕組みのこと。

Reactは単一ページ(SPA)なので、ページ遷移に見えても実際はコンポーネントの切り替えで画面を更新しています。その切り替えを管理してくれるのが react-router-dom です。


2.インストール方法

npm i react-router-dom

www.npmjs.com


3.よく使うコンポーネント・フック

コンポーネント/フック 役割
BrowserRouter 履歴管理付きのルーター。普通のWebアプリはこれを使う
Routes ルート定義をまとめる
Route URLパスとコンポーネントを紐付け
Link アプリ内リンク(<a>タグ代わり)
Outlet ネストされたルートの表示位置
Navigate コードからページ遷移(リダイレクト)
useParams URLパラメータ取得
useNavigate ページ遷移用の関数取得

 


4.ルーティング例

<Route index>は「親ルートにアクセスしたときのデフォルトの子ルート」を設定するための属性です。

上記の例では

  • /にアクセスすると自動的に<Home />が表示される
  • /reportにアクセスすると<Report />が表示される

という動きになります。


5.共通レイアウトを作るAppLayout

ルーティング例を見ていただくと、AppLayoutコンポーネントが親ルートとしてあり、Homeコンポーネント、ReportコンポーネントがAppLayoutコンポーネントの子ルートとして配置されています。

なぜAppLayoutを用意するのか?

  • ヘッダーやフッターなど全ページ共通UIをまとめて管理できる
  • <Outlet />で子ルートの表示位置を制御できる
  • 認証チェックやエラーハンドリングを一括で適用できる

コードの動き

  • <Outlet />の場所に子ルートのコンポーネントが差し込まれる
  • /<Home />/report<Report />が表示される
  • AppLayoutコンポーネントにヘッダーやナビゲーションを書けば常に共通で表示される

6.シングルページアプリケーション(SPA)とは

Single Page Applicationの略で、最初に読み込むHTMLは1枚だけで、ページ遷移はJavaScriptで中身だけ差し替える仕組みのWebアプリのことです。

MPA(Multi Page Application)との違い

特徴 SPA MPA
HTML読み込み 最初の1回のみ ページごとに読み込み
ページ切り替え JSで中身だけ切り替え ページ全体を再読み込み
表示速度 高速 遅め
SEO 対策が必要 標準で有利

Reactでページ切り替えを行うには、react-router-domなどのルーターライブラリを用いることで実現します。


7.まとめ

  • react-router-domはReactでのページ切り替えを管理するライブラリ
  • BrowserRouterRoutesRouteが基本
  • 共通UIはAppLayoutにまとめ、<Outlet />で子ルートを挿入
  • Route indexは親ルートのデフォルト表示を設定できる

Reactでアプリを作るとき、ルーターを正しく理解しておくと画面設計がグッと楽になりそうです。

Vite+TypeScriptでのReactプロジェクト作成手順

最近、Reactの勉強を行っています。
このブログでは、学んだことを随時まとめていこうと思います。
今回は「Reactの新しいプロジェクトをVite+TypeScriptで始める方法」について紹介します。


1. React新規プロジェクトをVite+TypeScriptで作成するコマンド

まず、下記コマンドをターミナルで実行します。

npm create vite@latest my-react-app -- --template react-ts
  • my-react-app は好きなプロジェクト名でOK

  • --template react-ts で「React + TypeScript」構成になる

続けて、以下のコマンドも実行します。

cd my-react-app
npm install
npm run dev

これらのコマンドはターミナルにも表示されるので安心ですね。

npm run devコマンドを実行するとターミナルに開発サーバーのURL(http://localhost:5173)が表示されます。

このURLをブラウザで開くと、Reactアプリが立ち上がります。
初期画面はこんな感じです。

Reactプロジェクトの作成は、とても簡単です。


2. npmとnpxの違い

ここでよく登場する「npm」や「npx」についても整理しておきます。

npmとは

  • Node.jsのパッケージ管理ツール。プロジェクトに必要なライブラリのインストールなどを担当。

  • 例:npm install react

npxとは

  • npmパッケージを一時的に実行するツール

  • インストールされていないパッケージでも自動的に探して、ローカルに一時的にインストールし実行。


3. npm createとnpx createの違い・使い分け

  • npm create は npm v6以降で使える新しい標準コマンド

  • npx create-xxx とほぼ同じ働きだが、npm createが今後の主流

  • npmのバージョンは npm -v で確認できる。6以上ならOK

調べると、上記のような違いがあるようです。

npm公式ドキュメントでもnpm createを推奨しているので、npm createコマンドを使用しておけば間違いなさそうです。
※npm createはnpm initのエイリアスとして動作

docs.npmjs.com


4. Viteって何?

  • Vite(ヴィート)は「超高速な開発サーバー&ビルドツール」

  • React/Vue/SvelteなどのモダンなWeb開発で、今もっともよく使われている

  • create-react-appwebpackよりもシンプル&高速

とてもいいなと思ったのは、開発サーバーがとにかく速いこと!ソースを修正すると、ブラウザに即反映されます。
最初に紹介した3つのコマンドで、即座にReactアプリが立ち上がるのは本当に便利です。

npm create vite@latest my-react-app -- --template react-ts
npm install
npm run dev

5. npm installの意味

npm createの実行直後は、まだパッケージはダウンロードされていません。
npm createは、package.jsonや初期ファイル一式を自動生成するだけです。
npm installコマンドを実行することで、package.jsonの内容に従い必要なパッケージが「node_modules」フォルダにインストールされるとのことです。


6. まとめ

Vite + TypeScriptを使ったReactプロジェクトの作成は、コマンド数も少なくスピーディーでとてもおすすめです。

npmやnpxの違い、npm installの役割も押さえておくと、今後の開発がスムーズになりそうです。