Web開発

TypeScript入門2026年版 - JavaScriptエンジニアが知るべき型安全な開発手法

カービー
TypeScript入門2026年版 - JavaScriptエンジニアが知るべき型安全な開発手法
#TypeScript#JavaScript#型安全#Web開発#初心者

2026年最新のTypeScriptを基礎から実践まで徹底解説。現役エンジニアが実際のコードを交えながら、型安全なWeb開発の手法をわかりやすく説明します。

TypeScript入門2026年版 - JavaScriptエンジニアが知るべき型安全な開発

JavaScript開発者にとって、TypeScriptの習得は2026年現在、もはや必須スキルとなりました。この記事では、TypeScriptの基礎から実践的な使い方まで、現役エンジニアの視点でわかりやすく解説します。

なぜTypeScriptを学ぶべきなのか?

2026年の現状

  • 求人の80%以上がTypeScript必須またはTypeScript歓迎
  • 平均年収798万円と、JavaScript(631万円)より大幅に高収入
  • Meta, Google, Microsoftなど大手企業が積極採用
  • React, Vue, Angularの公式サポートが充実

TypeScriptのメリット

// JavaScript(型がない)
function calculateTotal(price, tax) {
  return price + (price * tax); // 実行時にエラーが起こる可能性
}

// TypeScript(型がある)
function calculateTotal(price: number, tax: number): number {
  return price + (price * tax); // コンパイル時にエラーを検出
}

主な利点:

  • 🛡️ 型安全性 - 実行前にエラーを検出
  • 🔧 優秀な補完機能 - IDEサポートが圧倒的
  • 📚 自己文書化 - コードが仕様書の役割も果たす
  • 🚀 大規模開発への対応 - チーム開発で威力を発揮

開発環境の準備

Node.jsとTypeScriptのインストール

# Node.js 20.x以上をインストール済みの前提

# TypeScript をグローバルインストール
npm install -g typescript

# TypeScript コンパイラのバージョン確認
tsc --version

# プロジェクトの初期化
mkdir typescript-tutorial
cd typescript-tutorial
npm init -y

# 開発依存関係のインストール
npm install --save-dev typescript @types/node ts-node

# TypeScript設定ファイルの生成
npx tsc --init

VSCodeでの開発環境設定

推奨拡張機能:

{
  "recommendations": [
    "ms-vscode.vscode-typescript-next",
    "bradlc.vscode-tailwindcss",
    "esbenp.prettier-vscode",
    "ms-vscode.vscode-json"
  ]
}

tsconfig.json の基本設定

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "declaration": true,
    "removeComments": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

TypeScriptの基本型

プリミティブ型

// 基本的な型注釈
let userName: string = "田中太郎"
let age: number = 25
let isStudent: boolean = false
let height: number = 170.5

// undefined と null
let maybeString: string | undefined = undefined
let nullableNumber: number | null = null

// リテラル型(値そのものが型)
let direction: "north" | "south" | "east" | "west" = "north"
let statusCode: 200 | 404 | 500 = 200

配列とオブジェクト

// 配列の型注釈
let numbers: number[] = [1, 2, 3, 4, 5]
let fruits: Array<string> = ["apple", "banana", "orange"]
let mixed: (string | number)[] = ["apple", 1, "banana", 2]

// オブジェクトの型注釈
let user: {
  name: string
  age: number
  email?: string // ? = オプショナルプロパティ
} = {
  name: "田中太郎",
  age: 25
}

// ネストしたオブジェクト
let company: {
  name: string
  address: {
    prefecture: string
    city: string
    zipCode: string
  }
  employees: {
    name: string
    position: string
  }[]
} = {
  name: "株式会社サンプル",
  address: {
    prefecture: "東京都",
    city: "渋谷区",
    zipCode: "150-0001"
  },
  employees: [
    { name: "田中太郎", position: "エンジニア" },
    { name: "佐藤花子", position: "デザイナー" }
  ]
}

関数の型注釈

// 基本的な関数の型注釈
function greet(name: string): string {
  return `こんにちは、${name}さん!`
}

// アロー関数での型注釈
const add = (a: number, b: number): number => a + b

// オプショナル引数
function createUser(name: string, email?: string): object {
  if (email) {
    return { name, email }
  }
  return { name }
}

// デフォルト引数
function power(base: number, exponent: number = 2): number {
  return Math.pow(base, exponent)
}

// Rest パラメータ
function sum(...numbers: number[]): number {
  return numbers.reduce((total, num) => total + num, 0)
}

// 関数型の定義
type Calculator = (a: number, b: number) => number

const multiply: Calculator = (a, b) => a * b
const divide: Calculator = (a, b) => a / b

インターフェース(Interface)

基本的なインターフェース

// ユーザー情報のインターフェース
interface User {
  readonly id: number        // readonly = 読み取り専用
  name: string
  email: string
  age?: number              // オプショナル
  createdAt: Date
}

// 使用例
const newUser: User = {
  id: 1,
  name: "田中太郎",
  email: "tanaka@example.com",
  createdAt: new Date()
}

// newUser.id = 2  // エラー!readonly プロパティは変更不可

インターフェースの継承

// 基本インターフェース
interface Person {
  name: string
  age: number
}

// 継承したインターフェース
interface Employee extends Person {
  employeeId: string
  department: string
  salary: number
}

// 複数継承
interface ContactInfo {
  email: string
  phone?: string
}

interface EmployeeWithContact extends Employee, ContactInfo {
  startDate: Date
}

// 実装例
const employee: EmployeeWithContact = {
  name: "佐藤花子",
  age: 28,
  employeeId: "EMP-001",
  department: "開発部",
  salary: 6000000,
  email: "sato@company.com",
  phone: "090-1234-5678",
  startDate: new Date("2024-04-01")
}

メソッドを持つインターフェース

interface Calculator {
  add(a: number, b: number): number
  subtract(a: number, b: number): number
  multiply(a: number, b: number): number
  divide(a: number, b: number): number
}

class BasicCalculator implements Calculator {
  add(a: number, b: number): number {
    return a + b
  }
  
  subtract(a: number, b: number): number {
    return a - b
  }
  
  multiply(a: number, b: number): number {
    return a * b
  }
  
  divide(a: number, b: number): number {
    if (b === 0) {
      throw new Error("0で割ることはできません")
    }
    return a / b
  }
}

const calc = new BasicCalculator()
console.log(calc.add(10, 5)) // 15

型エイリアス(Type Aliases)

基本的な型エイリアス

// 複雑な型に名前を付ける
type Status = "loading" | "success" | "error"
type UserId = number
type UserName = string

// 関数型のエイリアス
type EventHandler = (event: MouseEvent) => void
type ApiResponse<T> = {
  data: T
  status: number
  message: string
}

// 使用例
let currentStatus: Status = "loading"
let userId: UserId = 12345

const handleClick: EventHandler = (event) => {
  console.log("クリックされました!", event)
}

Union型とIntersection型

// Union型(いずれかの型)
type StringOrNumber = string | number

function formatValue(value: StringOrNumber): string {
  if (typeof value === "string") {
    return value.toUpperCase()  // string のメソッドが使える
  } else {
    return value.toString()     // number のメソッドが使える
  }
}

// Intersection型(すべての型を満たす)
type PersonInfo = {
  name: string
  age: number
}

type ContactInfo = {
  email: string
  phone: string
}

type PersonWithContact = PersonInfo & ContactInfo

const person: PersonWithContact = {
  name: "田中太郎",
  age: 30,
  email: "tanaka@example.com",
  phone: "090-1234-5678"
}

ジェネリクス(Generics)

基本的なジェネリクス

// ジェネリック関数
function identity<T>(arg: T): T {
  return arg
}

// 使用例
let str = identity<string>("Hello")  // string型
let num = identity<number>(42)       // number型
let bool = identity(true)            // 型推論でboolean型

// 配列を扱うジェネリック関数
function getFirst<T>(array: T[]): T | undefined {
  return array.length > 0 ? array[0] : undefined
}

const firstNumber = getFirst([1, 2, 3])    // number | undefined
const firstString = getFirst(["a", "b"])   // string | undefined

ジェネリックインターフェース

interface ApiResponse<T> {
  data: T
  success: boolean
  message: string
}

interface User {
  id: number
  name: string
  email: string
}

interface Product {
  id: number
  name: string
  price: number
}

// 使用例
const userResponse: ApiResponse<User> = {
  data: { id: 1, name: "田中太郎", email: "tanaka@example.com" },
  success: true,
  message: "ユーザー情報を取得しました"
}

const productResponse: ApiResponse<Product[]> = {
  data: [
    { id: 1, name: "ノートPC", price: 80000 },
    { id: 2, name: "マウス", price: 2000 }
  ],
  success: true,
  message: "商品一覧を取得しました"
}

制約付きジェネリクス

// extends で型制約を設定
interface Lengthwise {
  length: number
}

function logLength<T extends Lengthwise>(arg: T): T {
  console.log(arg.length)  // length プロパティが存在することが保証される
  return arg
}

logLength("Hello")           // OK: string has length
logLength([1, 2, 3])        // OK: array has length
logLength({ length: 10, value: 3 }) // OK: has length property
// logLength(3)             // エラー: number には length プロパティがない

// keyof 制約
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key]
}

const person = {
  name: "田中太郎",
  age: 30,
  email: "tanaka@example.com"
}

const name = getProperty(person, "name")    // string型
const age = getProperty(person, "age")      // number型
// const invalid = getProperty(person, "height") // エラー: プロパティが存在しない

実践的なTypeScriptパターン

APIレスポンスの型定義

// API レスポンスの共通型
interface BaseResponse {
  success: boolean
  message: string
  timestamp: string
}

interface SuccessResponse<T> extends BaseResponse {
  success: true
  data: T
}

interface ErrorResponse extends BaseResponse {
  success: false
  error: {
    code: string
    details?: string
  }
}

type ApiResponse<T> = SuccessResponse<T> | ErrorResponse

// 実際のAPI関数
async function fetchUser(id: number): Promise<ApiResponse<User>> {
  try {
    const response = await fetch(`/api/users/${id}`)
    const data = await response.json()
    
    if (response.ok) {
      return {
        success: true,
        data,
        message: "ユーザー情報を取得しました",
        timestamp: new Date().toISOString()
      }
    } else {
      return {
        success: false,
        message: "ユーザーの取得に失敗しました",
        timestamp: new Date().toISOString(),
        error: {
          code: response.status.toString(),
          details: data.message
        }
      }
    }
  } catch (error) {
    return {
      success: false,
      message: "ネットワークエラーが発生しました",
      timestamp: new Date().toISOString(),
      error: {
        code: "NETWORK_ERROR",
        details: error instanceof Error ? error.message : "Unknown error"
      }
    }
  }
}

// 使用例(型ガードを使った安全な処理)
async function handleUserFetch(userId: number) {
  const result = await fetchUser(userId)
  
  if (result.success) {
    // この時点で result.data は User 型として扱われる
    console.log(`ユーザー名: ${result.data.name}`)
    console.log(`メールアドレス: ${result.data.email}`)
  } else {
    // この時点で result.error が利用可能
    console.error(`エラー: ${result.error.code}`)
    console.error(`詳細: ${result.error.details}`)
  }
}

React with TypeScript

import React, { useState, useEffect } from 'react'

// Props の型定義
interface UserCardProps {
  user: User
  onEdit?: (user: User) => void
  onDelete?: (userId: number) => void
}

// React コンポーネント
const UserCard: React.FC<UserCardProps> = ({ user, onEdit, onDelete }) => {
  const [isEditing, setIsEditing] = useState<boolean>(false)
  
  const handleEdit = () => {
    if (onEdit) {
      onEdit(user)
    }
    setIsEditing(false)
  }
  
  const handleDelete = () => {
    if (onDelete && confirm('このユーザーを削除してもよろしいですか?')) {
      onDelete(user.id)
    }
  }
  
  return (
    <div className="user-card">
      <h3>{user.name}</h3>
      <p>{user.email}</p>
      {user.age && <p>年齢: {user.age}歳</p>}
      
      <div className="actions">
        <button onClick={handleEdit}>編集</button>
        <button onClick={handleDelete}>削除</button>
      </div>
    </div>
  )
}

// カスタムフック
interface UseUsersReturn {
  users: User[]
  loading: boolean
  error: string | null
  refreshUsers: () => Promise<void>
}

function useUsers(): UseUsersReturn {
  const [users, setUsers] = useState<User[]>([])
  const [loading, setLoading] = useState<boolean>(true)
  const [error, setError] = useState<string | null>(null)
  
  const fetchUsers = async () => {
    try {
      setLoading(true)
      setError(null)
      
      const response = await fetch('/api/users')
      if (!response.ok) {
        throw new Error('ユーザーの取得に失敗しました')
      }
      
      const data = await response.json()
      setUsers(data)
    } catch (err) {
      setError(err instanceof Error ? err.message : '未知のエラー')
    } finally {
      setLoading(false)
    }
  }
  
  useEffect(() => {
    fetchUsers()
  }, [])
  
  return {
    users,
    loading,
    error,
    refreshUsers: fetchUsers
  }
}

Express with TypeScript

import express, { Request, Response, NextFunction } from 'express'

// リクエストボディの型定義
interface CreateUserRequest {
  name: string
  email: string
  age?: number
}

interface UpdateUserRequest {
  name?: string
  email?: string
  age?: number
}

// 拡張されたRequest型
interface TypedRequest<T> extends Request {
  body: T
}

// エラーレスポンスの型
interface ErrorResponse {
  error: string
  details?: string
}

// Express アプリケーション
const app = express()
app.use(express.json())

// ユーザー作成エンドポイント
app.post('/api/users', async (
  req: TypedRequest<CreateUserRequest>,
  res: Response<ApiResponse<User> | ErrorResponse>
) => {
  try {
    const { name, email, age } = req.body
    
    // バリデーション
    if (!name || !email) {
      return res.status(400).json({
        error: 'name と email は必須です'
      })
    }
    
    // ユーザー作成(実際はDBアクセス)
    const newUser: User = {
      id: Date.now(),
      name,
      email,
      age,
      createdAt: new Date()
    }
    
    res.status(201).json({
      success: true,
      data: newUser,
      message: 'ユーザーを作成しました',
      timestamp: new Date().toISOString()
    })
  } catch (error) {
    res.status(500).json({
      error: 'サーバーエラーが発生しました',
      details: error instanceof Error ? error.message : '未知のエラー'
    })
  }
})

// パラメータ付きルート
interface UserParamsRequest extends Request {
  params: {
    id: string
  }
}

app.get('/api/users/:id', async (
  req: UserParamsRequest,
  res: Response<ApiResponse<User> | ErrorResponse>
) => {
  try {
    const userId = parseInt(req.params.id, 10)
    
    if (isNaN(userId)) {
      return res.status(400).json({
        error: '無効なユーザーIDです'
      })
    }
    
    // ユーザー取得(実際はDBアクセス)
    const user = await findUserById(userId)
    
    if (!user) {
      return res.status(404).json({
        error: 'ユーザーが見つかりません'
      })
    }
    
    res.json({
      success: true,
      data: user,
      message: 'ユーザー情報を取得しました',
      timestamp: new Date().toISOString()
    })
  } catch (error) {
    res.status(500).json({
      error: 'サーバーエラーが発生しました',
      details: error instanceof Error ? error.message : '未知のエラー'
    })
  }
})

高度な型操作

Utility Types

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

// Required<T> - すべてのプロパティを必須に
interface OptionalUser {
  id?: number
  name?: string
  email?: string
}

type RequiredUser = Required<OptionalUser>
// {
//   id: number
//   name: string
//   email: string
// }

// Pick<T, K> - 指定したプロパティのみを抜き出す
type UserSummary = Pick<User, 'id' | 'name' | 'email'>
// {
//   id: number
//   name: string
//   email: string
// }

// Omit<T, K> - 指定したプロパティを除外
type CreateUserData = Omit<User, 'id' | 'createdAt'>
// {
//   name: string
//   email: string
//   age?: number
// }

// Record<K, T> - キーと値の型を指定してオブジェクト型を作成
type UserRoles = Record<string, 'admin' | 'user' | 'moderator'>
// {
//   [key: string]: 'admin' | 'user' | 'moderator'
// }

const roleAssignments: UserRoles = {
  "user1": "admin",
  "user2": "user",
  "user3": "moderator"
}

条件型(Conditional Types)

// 条件型の基本
type IsString<T> = T extends string ? true : false

type Test1 = IsString<string>  // true
type Test2 = IsString<number>  // false

// より実用的な例
type NonNullable<T> = T extends null | undefined ? never : T

type SafeString = NonNullable<string | null>  // string
type SafeNumber = NonNullable<number | undefined>  // number

// 関数の戻り値の型を抽出
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any

function getUserName(): string {
  return "田中太郎"
}

type UserNameType = ReturnType<typeof getUserName>  // string

デバッグとエラーハンドリング

型アサーション

// 型アサーション(型を強制的に指定)
const userInput = document.getElementById('user-input') as HTMLInputElement

// より安全な方法(型ガード)
function isHTMLInputElement(element: Element | null): element is HTMLInputElement {
  return element !== null && element.tagName === 'INPUT'
}

const maybeInput = document.getElementById('user-input')
if (isHTMLInputElement(maybeInput)) {
  // この時点で maybeInput は HTMLInputElement として扱われる
  console.log(maybeInput.value)
}

よくあるエラーと解決策

// ❌ よくあるエラー1: null チェック不足
function processUser(user: User | null) {
  console.log(user.name)  // エラー: user が null の可能性
}

// ✅ 解決策: Optional Chaining
function processUserSafely(user: User | null) {
  console.log(user?.name ?? 'Unknown User')
}

// ❌ よくあるエラー2: 型の不一致
function addNumbers(a: string, b: string): number {
  return a + b  // エラー: string + string は string を返す
}

// ✅ 解決策: 適切な型変換
function addNumbersCorrectly(a: string, b: string): number {
  return Number(a) + Number(b)
}

// ❌ よくあるエラー3: 配列要素の型チェック不足
function processItems(items: any[]) {
  items.forEach(item => {
    console.log(item.name)  // item に name プロパティがあるか不明
  })
}

// ✅ 解決策: 型ガードを使用
interface NamedItem {
  name: string
}

function hasName(item: any): item is NamedItem {
  return typeof item === 'object' && item !== null && typeof item.name === 'string'
}

function processItemsSafely(items: any[]) {
  items.forEach(item => {
    if (hasName(item)) {
      console.log(item.name)  // 型安全
    }
  })
}

学習ロードマップとリソース

30日間学習プラン

【第1週:基礎固め】
1-2日目: TypeScript環境構築・基本型
3-4日目: インターフェースと型エイリアス
5-7日目: 関数とジェネリクスの基礎

【第2週:実践入門】
8-10日目: クラスとモジュール
11-12日目: React with TypeScript
13-14日目: 実際のプロジェクト作成

【第3週:応用技術】
15-17日目: 高度な型操作(Utility Types)
18-19日目: Express with TypeScript
20-21日目: エラーハンドリング・デバッグ

【第4週:実践・応用】
22-24日目: 大規模プロジェクトでの型設計
25-26日目: テスト(Jest + TypeScript)
27-30日目: 個人プロジェクト完成・リファクタリング

おすすめ学習リソース

公式・基本リソース:

実践的リソース:

書籍:

  • 『プロを目指すTypeScript』- 実務レベルまで対応
  • 『TypeScriptハンズオン』- 実践的なコード例が豊富

まとめ

TypeScriptは、JavaScriptの動的な特性を保ちながら型安全性を提供する優れた言語です。学習コストはありますが、以下のメリットは非常に大きいです:

短期的メリット

  • 🐛 実行前のエラー検出 - デバッグ時間の大幅短縮
  • 🧠 優秀なIDE支援 - コード補完・リファクタリングサポート
  • 📝 コードの自己文書化 - 型情報がドキュメントとして機能

長期的メリット

  • 💼 転職・案件獲得の有利性 - 高年収求人の必須スキル
  • 👥 チーム開発での生産性向上 - 大規模プロジェクトでの威力
  • 🚀 最新技術への対応 - React, Vue, Node.jsでの標準化

次のアクションプラン

  1. 今日から始める - TypeScript Playgroundで基本型を試してみる
  2. 実際にプロジェクト作成 - 簡単なTodoアプリをTypeScriptで作成
  3. 継続的学習 - 週に2-3時間の定期的な学習習慣を構築

TypeScriptは現代のWeb開発において必須スキルです。ぜひこの記事を参考に、型安全で保守性の高いコード作成にチャレンジしてみてください!


始めの一歩: まずは既存のJavaScriptプロジェクトの拡張子を.jsから.tsに変更し、段階的に型注釈を追加してみましょう。小さな改善の積み重ねが、大きな成長につながります!