React+TypeScript環境構築|型安全な開発環境を30分で構築

React+TypeScriptの開発環境を30分で構築する完全ガイド。型安全な設定、VS Code拡張機能、実践的なコード例まで詳しく解説

Learning Next 運営
39 分で読めます

React+TypeScript環境構築|型安全な開発環境を30分で構築

みなさん、こんな悩みはありませんか?

「ReactでTypeScriptを使いたいけど、設定が複雑そう...」 「型安全な開発環境って、どうやって作るの?」

TypeScriptを導入すると、開発がとても楽になります。 でも、最初の設定でつまずいてしまう方も多いんですよね。

大丈夫です! この記事では、30分でReact+TypeScript環境を作る方法をお教えします。

難しそうに見えますが、実は意外と簡単なんです。 一緒に、安全で効率的な開発環境を作っていきましょう。

TypeScript導入のメリット

まず、なぜReactでTypeScriptを使うといいのか見てみましょう。

型安全性による品質向上

TypeScriptの最大のメリットは、エラーを事前に見つけられることです。

// ❌ JavaScript の場合(実行時エラー)
function calculateTotal(price, tax) {
  return price * tax;
}

const result = calculateTotal("100", 0.1); 
// "1001001001001..." という変な文字列になる

上のコードを見てください。 JavaScriptだと、実際に動かすまでエラーに気づけません。

でも、TypeScriptなら違います。

// ✅ TypeScript の場合(コンパイル時エラー)
function calculateTotal(price: number, tax: number): number {
  return price * tax;
}

// const result = calculateTotal("100", 0.1); // エラーになる
const result = calculateTotal(100, 0.1); // 正しい使い方

TypeScriptでは、引数の型を指定しています。 文字列を渡そうとすると、コードを書いている時点でエラーが出ます。

実行する前に問題が分かるって、すごく便利ですよね。

IntelliSenseによる開発効率向上

TypeScriptを使うと、VS Codeの自動補完がとても賢くなります。

// ユーザー情報の型定義
interface User {
  id: number;
  name: string;
  email: string;
  createdAt: Date;
}

function displayUser(user: User) {
  // user. と入力すると、候補が自動で表示される
  console.log(user.name); // 型安全
  // console.log(user.age); // 存在しないのでエラー
}

user.と入力すると、idnameemailcreatedAtが候補として出てきます。 もう、プロパティ名を間違える心配はありません。

これだけでも、コーディングスピードが格段に上がりますよ。

リファクタリングの安全性

大きなアプリでも、TypeScriptがあれば安心してコードを変更できます。

interface Product {
  id: number;
  name: string;
  price: number;
  // description: string; // このプロパティを削除すると
}

function ProductCard({ product }: { product: Product }) {
  return (
    <div>
      <h3>{product.name}</h3>
      <p>¥{product.price}</p>
      {/* <p>{product.description}</p> // ここでエラーが出る */}
    </div>
  );
}

descriptionプロパティを削除すると、使っている箇所すべてでエラーが表示されます。 見落としがないので、バグを防げるんです。

こんな風に、TypeScriptは開発者の強い味方になってくれます。

環境構築の手順(30分で完了)

それでは、実際にReact+TypeScript環境を作っていきましょう。

ステップ1:前提条件の確認(5分)

まず、必要なツールが入っているか確認します。

# Node.js のバージョン確認(18.0以上がおすすめ)
node --version

# npm のバージョン確認(8.0以上がおすすめ)
npm --version

コマンドを実行してみてください。 バージョン番号が表示されればOKです。

Node.jsがインストールされていない場合は、公式サイトからダウンロードしてくださいね。 難しい操作はありません。

ステップ2:プロジェクト作成(5分)

Create React Appを使って、TypeScriptプロジェクトを作ります。

# TypeScript テンプレートでプロジェクト作成
npx create-react-app my-typescript-app --template typescript

# プロジェクトディレクトリに移動
cd my-typescript-app

# 開発サーバーを起動して動作確認
npm start

上のコマンドを順番に実行してください。

最後のnpm startを実行すると、ブラウザが開いてReactのページが表示されます。 これで、基本的なTypeScriptプロジェクトが完成しました。

簡単でしょう?

作成されるファイル構造はこんな感じです。

my-typescript-app/
├── src/
│   ├── App.tsx          # TypeScript React コンポーネント
│   ├── App.test.tsx     # テストファイル
│   ├── index.tsx        # エントリーポイント
│   └── ...
├── package.json
├── tsconfig.json        # TypeScript 設定ファイル
└── ...

.tsxという拡張子に注目してください。 これがTypeScript版のReactファイルです。

ステップ3:TypeScript設定の最適化(10分)

tsconfig.jsonを編集して、より厳密な型チェックを有効にします。

{
  "compilerOptions": {
    "target": "es5",
    "lib": [
      "dom",
      "dom.iterable",
      "es6"
    ],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "noImplicitAny": true,
    "noImplicitReturns": true,
    "noImplicitThis": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "exactOptionalPropertyTypes": true,
    "forceConsistentCasingInFileNames": true,
    "noFallthroughCasesInSwitch": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx"
  },
  "include": [
    "src/**/*"
  ],
  "exclude": [
    "node_modules",
    "build"
  ]
}

この設定で、TypeScriptがより厳密にコードをチェックしてくれます。

strict: trueは、厳密な型チェックを有効にする設定です。 noImplicitAny: trueは、any型の暗黙的な使用を禁止します。

最初は警告が多く出るかもしれません。 でも大丈夫です。 これらの設定が、より安全なコードを書く手助けをしてくれますよ。

ステップ4:開発支援ツールの追加(5分)

開発を楽にするパッケージを追加しましょう。

# 型定義とユーティリティ型の追加
npm install --save-dev @types/node @types/react @types/react-dom

# コード品質管理ツール
npm install --save-dev eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin

# Prettier(コードフォーマッター)
npm install --save-dev prettier eslint-config-prettier eslint-plugin-prettier

# よく使用されるユーティリティライブラリ
npm install clsx classnames

# 型定義
npm install --save-dev @types/classnames

これらのツールを入れることで、開発がとても楽になります。

ESLintは、コードの問題点を教えてくれます。 Prettierは、コードを自動で整形してくれます。

チーム開発では特に重要なツールですね。

ステップ5:ESLintとPrettierの設定(5分)

最後に、コード品質管理ツールの設定をします。

.eslintrc.jsファイルを作成してください。

module.exports = {
  parser: '@typescript-eslint/parser',
  parserOptions: {
    ecmaVersion: 2020,
    sourceType: 'module',
    ecmaFeatures: {
      jsx: true,
    },
  },
  settings: {
    react: {
      version: 'detect',
    },
  },
  extends: [
    'react-app',
    'react-app/jest',
    '@typescript-eslint/recommended',
    'prettier',
  ],
  plugins: ['@typescript-eslint', 'prettier'],
  rules: {
    'prettier/prettier': 'error',
    '@typescript-eslint/no-unused-vars': 'error',
    '@typescript-eslint/explicit-function-return-type': 'warn',
    '@typescript-eslint/no-explicit-any': 'error',
    'react/prop-types': 'off',
  },
};

この設定で、TypeScriptとReactに最適化されたコードチェックができます。

続いて、.prettierrcファイルも作成しましょう。

{
  "semi": true,
  "trailingComma": "es5",
  "singleQuote": true,
  "printWidth": 80,
  "tabWidth": 2,
  "useTabs": false
}

これで、コードが自動で綺麗に整形されるようになります。

いちいち手動で整形する必要がないので、とても便利ですよ。

VS Code拡張機能とツール設定

開発効率を最大化するVS Code設定も見ていきましょう。

必須拡張機能

以下の拡張機能をVS Codeにインストールしてください。

.vscode/extensions.jsonファイルを作成して、推奨拡張機能を指定できます。

{
  "recommendations": [
    "ms-vscode.vscode-typescript-next",
    "esbenp.prettier-vscode",
    "ms-vscode.vscode-eslint",
    "formulahendry.auto-rename-tag",
    "christian-kohler.path-intellisense",
    "ms-vscode.vscode-react-native"
  ]
}

これらの拡張機能があると、開発がとても楽になります。

特にvscode-typescript-nextは、TypeScriptサポートを強化してくれます。 prettier-vscodeは、保存時にコードを自動整形してくれますよ。

VS Code設定ファイル

.vscode/settings.jsonを作成して、プロジェクト固有の設定をしましょう。

{
  "typescript.preferences.quoteStyle": "single",
  "typescript.suggest.autoImports": true,
  "typescript.updateImportsOnFileMove.enabled": "always",
  
  "editor.formatOnSave": true,
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  
  "eslint.validate": [
    "javascript",
    "javascriptreact",
    "typescript",
    "typescriptreact"
  ],
  "eslint.run": "onSave",
  
  "editor.tabSize": 2,
  "editor.insertSpaces": true,
  "editor.wordWrap": "on",
  
  "emmet.includeLanguages": {
    "typescriptreact": "html"
  }
}

この設定で、VS Codeがより便利になります。

formatOnSave: trueは、ファイル保存時に自動でコード整形してくれる設定です。 これがあると、いつもコードが綺麗な状態を保てますよ。

型定義の実践的な使い方

実際の開発で使う型定義パターンを学んでいきましょう。

基本的なコンポーネントの型定義

まず、よく使う型定義から見てみましょう。

// types/index.ts - 共通の型定義
export interface User {
  id: number;
  name: string;
  email: string;
  avatar?: string; // ?マークでオプショナル
  role: 'admin' | 'user' | 'guest'; // Union型
  preferences: {
    theme: 'light' | 'dark';
    notifications: boolean;
  };
  createdAt: Date;
}

export interface Product {
  id: string;
  name: string;
  description: string;
  price: number;
  category: string;
  inStock: boolean;
  images: string[];
  tags: string[];
}

この例では、UserProductのインターフェースを定義しています。

avatar??マークは、オプショナルプロパティを意味します。 あってもなくても大丈夫な項目ですね。

role: 'admin' | 'user' | 'guest'は、Union型といいます。 指定した値のいずれかしか使えないようになります。

このように型を定義することで、データの構造が明確になります。

Propsの型定義

Reactコンポーネントのプロップスも、しっかり型定義しましょう。

// components/UserCard.tsx
import React from 'react';
import { User } from '../types';

interface UserCardProps {
  user: User;
  showEmail?: boolean;
  onEdit?: (user: User) => void;
  onDelete?: (userId: number) => void;
  className?: string;
}

export const UserCard: React.FC<UserCardProps> = ({
  user,
  showEmail = false,
  onEdit,
  onDelete,
  className = '',
}) => {
  const handleEdit = (): void => {
    if (onEdit) {
      onEdit(user);
    }
  };

  const handleDelete = (): void => {
    if (onDelete && window.confirm('本当に削除しますか?')) {
      onDelete(user.id);
    }
  };

  return (
    <div className={`user-card ${className}`}>
      <div className="user-info">
        {user.avatar && (
          <img src={user.avatar} alt={user.name} />
        )}
        <div>
          <h3>{user.name}</h3>
          {showEmail && <p>{user.email}</p>}
          <span>{user.role}</span>
        </div>
      </div>
      
      <div className="user-actions">
        {onEdit && (
          <button onClick={handleEdit}>
            編集
          </button>
        )}
        {onDelete && (
          <button onClick={handleDelete}>
            削除
          </button>
        )}
      </div>
    </div>
  );
};

この例では、UserCardPropsインターフェースでプロップスの型を定義しています。

onEdit?: (user: User) => voidは、関数のプロップスです。 Userを受け取って、何も返さない(void)関数ですね。

?マークが付いているので、この関数を渡すかどうかは任意です。

このように型を定義すると、コンポーネントを使う時にどんなプロップスが必要か一目で分かります。

フックとイベントハンドラーの型定義

カスタムフックやイベントハンドラーも、型定義が重要です。

// hooks/useApi.ts - カスタムフックの型定義
import { useState, useEffect } from 'react';

interface UseApiState<T> {
  data: T | null;
  loading: boolean;
  error: string | null;
}

interface UseApiReturn<T> extends UseApiState<T> {
  refetch: () => Promise<void>;
}

export function useApi<T>(url: string): UseApiReturn<T> {
  const [state, setState] = useState<UseApiState<T>>({
    data: null,
    loading: true,
    error: null,
  });

  const fetchData = async (): Promise<void> => {
    try {
      setState(prev => ({ ...prev, loading: true, error: null }));
      
      const response = await fetch(url);
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }
      
      const result = await response.json();
      setState({
        data: result,
        loading: false,
        error: null,
      });
    } catch (error) {
      setState({
        data: null,
        loading: false,
        error: error instanceof Error ? error.message : 'Unknown error',
      });
    }
  };

  useEffect(() => {
    fetchData();
  }, [url]);

  return {
    ...state,
    refetch: fetchData,
  };
}

このuseApiフックは、ジェネリック型<T>を使っています。 どんなデータ型でも対応できる、汎用的なフックです。

UseApiReturn<T>インターフェースで、戻り値の型を定義しています。 dataloadingerrorの状態と、refetch関数を返します。

こんな風に使えます。

// 使用例
const { data: users, loading, error, refetch } = useApi<User[]>('/api/users');

<User[]>で、取得するデータがUserの配列だと指定しています。 これで、users変数がUser[]型になります。

型安全で、自動補完も効くので、とても使いやすいですよ。

よく使うユーティリティ型

TypeScriptには、便利なユーティリティ型があります。

基本的なユーティリティ型

よく使うものを見てみましょう。

// Partial - すべてのプロパティをオプショナルにする
type PartialUser = Partial<User>;
// { id?: number; name?: string; email?: string; ... }

// Pick - 特定のプロパティのみを抽出
type UserSummary = Pick<User, 'id' | 'name' | 'email'>;
// { id: number; name: string; email: string; }

// Omit - 特定のプロパティを除外
type CreateUserRequest = Omit<User, 'id' | 'createdAt'>;
// { name: string; email: string; avatar?: string; ... }

// Required - すべてのプロパティを必須にする
type RequiredUser = Required<User>;
// { id: number; name: string; email: string; avatar: string; ... }

これらのユーティリティ型を使うと、既存の型から新しい型を簡単に作れます。

Partial<User>は、ユーザー更新の時によく使います。 すべての項目が任意になるので、一部だけ更新する時に便利ですね。

Pick<User, 'id' | 'name'>は、必要な部分だけ抜き出せます。 APIのレスポンスで、一部の情報だけ欲しい時に使えますよ。

実践的な使用例

実際のプロジェクトでの使用例を見てみましょう。

// フォーム用の型定義
interface ContactForm {
  name: string;
  email: string;
  subject: string;
  message: string;
}

// バリデーションエラー用の型(すべてオプショナル)
type FormErrors = Partial<ContactForm>;

// フォームコンポーネント
export const ContactFormComponent: React.FC = () => {
  const [formData, setFormData] = useState<ContactForm>({
    name: '',
    email: '',
    subject: '',
    message: '',
  });

  const [errors, setErrors] = useState<FormErrors>({});
  const [isSubmitting, setIsSubmitting] = useState<boolean>(false);

  // バリデーション関数
  const validateForm = (): boolean => {
    const newErrors: FormErrors = {};

    if (!formData.name.trim()) {
      newErrors.name = '名前は必須です';
    }

    if (!formData.email.trim()) {
      newErrors.email = 'メールアドレスは必須です';
    } else if (!/\S+@\S+\.\S+/.test(formData.email)) {
      newErrors.email = '有効なメールアドレスを入力してください';
    }

    if (!formData.subject.trim()) {
      newErrors.subject = '件名は必須です';
    }

    if (!formData.message.trim()) {
      newErrors.message = 'メッセージは必須です';
    }

    setErrors(newErrors);
    return Object.keys(newErrors).length === 0;
  };

  // フォーム送信
  const handleSubmit = async (e: React.FormEvent): Promise<void> => {
    e.preventDefault();
    
    if (!validateForm()) return;

    setIsSubmitting(true);
    try {
      // API呼び出し
      const response = await fetch('/api/contact', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(formData),
      });

      if (response.ok) {
        alert('お問い合わせを送信しました');
        setFormData({ name: '', email: '', subject: '', message: '' });
      } else {
        throw new Error('送信に失敗しました');
      }
    } catch (error) {
      alert('送信に失敗しました。もう一度お試しください。');
    } finally {
      setIsSubmitting(false);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label htmlFor="name">名前 *</label>
        <input
          type="text"
          id="name"
          value={formData.name}
          onChange={(e) => setFormData(prev => ({ ...prev, name: e.target.value }))}
        />
        {errors.name && <span className="error">{errors.name}</span>}
      </div>

      <div>
        <label htmlFor="email">メールアドレス *</label>
        <input
          type="email"
          id="email"
          value={formData.email}
          onChange={(e) => setFormData(prev => ({ ...prev, email: e.target.value }))}
        />
        {errors.email && <span className="error">{errors.email}</span>}
      </div>

      <button type="submit" disabled={isSubmitting}>
        {isSubmitting ? '送信中...' : '送信'}
      </button>
    </form>
  );
};

この例では、Partial<ContactForm>を使って、エラーメッセージの型を定義しています。 すべてのフィールドがオプショナルなので、エラーがない項目はundefinedになります。

フォームの状態管理も型安全にできているので、typoやプロパティ名の間違いを防げますね。

実践的なプロジェクト例

実際の開発でよく使われる機能を実装してみましょう。

APIクライアントの実装

型安全なAPIクライアントを作ってみます。

// api/client.ts - 型安全なAPIクライアント
interface ApiResponse<T> {
  data: T;
  message: string;
  success: boolean;
  errors?: string[];
}

class ApiClient {
  private baseURL: string;
  private defaultHeaders: Record<string, string>;

  constructor(baseURL: string) {
    this.baseURL = baseURL;
    this.defaultHeaders = {
      'Content-Type': 'application/json',
    };
  }

  // 認証トークンの設定
  setAuthToken(token: string): void {
    this.defaultHeaders['Authorization'] = `Bearer ${token}`;
  }

  // GET リクエスト
  async get<T>(endpoint: string): Promise<ApiResponse<T>> {
    const response = await fetch(`${this.baseURL}${endpoint}`, {
      method: 'GET',
      headers: this.defaultHeaders,
    });

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    return response.json();
  }

  // POST リクエスト
  async post<T, U>(endpoint: string, data: U): Promise<ApiResponse<T>> {
    const response = await fetch(`${this.baseURL}${endpoint}`, {
      method: 'POST',
      headers: this.defaultHeaders,
      body: JSON.stringify(data),
    });

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    return response.json();
  }
}

// User API クラス
export class UserApi {
  constructor(private client: ApiClient) {}

  async getUsers(): Promise<User[]> {
    const response = await this.client.get<User[]>('/users');
    return response.data;
  }

  async getUser(id: number): Promise<User> {
    const response = await this.client.get<User>(`/users/${id}`);
    return response.data;
  }

  async createUser(userData: Omit<User, 'id' | 'createdAt'>): Promise<User> {
    const response = await this.client.post<User, Omit<User, 'id' | 'createdAt'>>('/users', userData);
    return response.data;
  }

  async updateUser(id: number, userData: Partial<User>): Promise<User> {
    const response = await this.client.put<User, Partial<User>>(`/users/${id}`, userData);
    return response.data;
  }
}

このAPIクライアントは、型安全にAPIを呼び出せます。

get<T>のようにジェネリック型を使って、レスポンスの型を指定できます。 createUserでは、Omit<User, 'id' | 'createdAt'>を使って、IDと作成日時を除いたユーザーデータを受け取ります。

こんな風に使えます。

// 使用例
const apiClient = new ApiClient('https://api.example.com');
const userApi = new UserApi(apiClient);

// 型安全なAPI呼び出し
const users: User[] = await userApi.getUsers();
const newUser: User = await userApi.createUser({
  name: '田中太郎',
  email: 'tanaka@example.com',
  role: 'user',
  preferences: {
    theme: 'light',
    notifications: true,
  },
});

戻り値の型が自動で推論されるので、自動補完も効きます。 とても使いやすいAPIクライアントですね。

トラブルシューティング

よくある問題と解決方法を見ていきましょう。

型エラーの解決

初心者がよく遭遇するエラーと、その解決方法です。

// 問題1: any型の使用
// ❌ 悪い例
function processData(data: any): any {
  return data.someProperty;
}

// ✅ 良い例:具体的な型を定義
interface DataType {
  someProperty: string;
  otherProperty: number;
}

function processData(data: DataType): string {
  return data.someProperty;
}

any型は避けましょう。 代わりに、具体的な型やインターフェースを定義してください。

// 問題2: nullの可能性
// ❌ エラーが発生する例
function getUserName(user: User | null): string {
  return user.name; // Object is possibly 'null'
}

// ✅ 修正例:null チェック
function getUserName(user: User | null): string {
  if (!user) {
    return 'Unknown';
  }
  return user.name;
}

// または Optional Chaining を使用
function getUserName(user: User | null): string {
  return user?.name ?? 'Unknown';
}

user?.nameは、usernullじゃない時だけnameにアクセスします。 ??は、左側がnullundefinedの時に右側の値を使うという意味です。

// 問題3: イベントハンドラーの型
// ❌ 型が不明確
function handleClick(event) { // エラー
  console.log(event.target);
}

// ✅ 修正例:適切な型を指定
function handleClick(event: React.MouseEvent<HTMLButtonElement>): void {
  console.log(event.currentTarget);
}

function handleInputChange(event: React.ChangeEvent<HTMLInputElement>): void {
  console.log(event.target.value);
}

Reactのイベントハンドラーには、専用の型があります。 React.MouseEventReact.ChangeEventを使いましょう。

これらの型を覚えておくと、エラーで悩むことが減りますよ。

まとめ

React+TypeScript環境構築の完全ガイドを解説しました。

30分で完成する環境構築

前提条件の確認から設定の最適化まで、ステップバイステップで解説しました。 思っていたより簡単だったのではないでしょうか?

型定義の実践

Props、フック、イベントハンドラーなど、実際の開発で必要な型定義方法を学びました。 最初は慣れないかもしれませんが、使っているうちに自然に身につきますよ。

開発効率の向上

VS Code拡張機能やユーティリティ型を活用することで、開発効率が大幅に向上します。 自動補完やエラーチェックのおかげで、安心してコードを書けるようになります。

実践的な例

APIクライアントやフォーム管理など、実際のプロジェクトで使える機能を実装しました。 これらの例を参考に、自分のプロジェクトでも活用してみてください。

トラブルシューティング

よくある問題と解決方法を知ることで、つまずいた時も安心です。 エラーメッセージを恐れずに、一つずつ解決していけば大丈夫です。

TypeScriptを導入することで、開発効率と品質が格段に向上します。

最初は覚えることが多く感じるかもしれません。 でも、一度慣れてしまえば、もうTypeScriptなしでは開発できなくなりますよ。

ぜひこの記事を参考にして、型安全なReact開発環境を構築してみてください。 きっと、より安全で効率的な開発体験を実感できるはずです!

大丈夫です。 少しずつ慣れていけば、必ずマスターできますよ。

関連記事