Web開発
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 Handbook - 公式ドキュメント
- TypeScript Playground - ブラウザで実行可能
- TypeScript Deep Dive - 無料の包括的ガイド
実践的リソース:
- React TypeScript Cheatsheets
- Type Challenges - 型の練習問題
- TypeScript Weekly - 週次ニュースレター
書籍:
- 『プロを目指すTypeScript』- 実務レベルまで対応
- 『TypeScriptハンズオン』- 実践的なコード例が豊富
まとめ
TypeScriptは、JavaScriptの動的な特性を保ちながら型安全性を提供する優れた言語です。学習コストはありますが、以下のメリットは非常に大きいです:
短期的メリット
- 🐛 実行前のエラー検出 - デバッグ時間の大幅短縮
- 🧠 優秀なIDE支援 - コード補完・リファクタリングサポート
- 📝 コードの自己文書化 - 型情報がドキュメントとして機能
長期的メリット
- 💼 転職・案件獲得の有利性 - 高年収求人の必須スキル
- 👥 チーム開発での生産性向上 - 大規模プロジェクトでの威力
- 🚀 最新技術への対応 - React, Vue, Node.jsでの標準化
次のアクションプラン
- 今日から始める - TypeScript Playgroundで基本型を試してみる
- 実際にプロジェクト作成 - 簡単なTodoアプリをTypeScriptで作成
- 継続的学習 - 週に2-3時間の定期的な学習習慣を構築
TypeScriptは現代のWeb開発において必須スキルです。ぜひこの記事を参考に、型安全で保守性の高いコード作成にチャレンジしてみてください!
始めの一歩: まずは既存のJavaScriptプロジェクトの拡張子を
.jsから.tsに変更し、段階的に型注釈を追加してみましょう。小さな改善の積み重ねが、大きな成長につながります!