TypeScript入門:JavaScriptからの移行ガイド
TypeScriptは、Microsoftが開発したJavaScriptのスーパーセットです。静的型付けを追加することで、より安全で保守性の高いコードを書くことができます。
TypeScriptとは?
TypeScriptは以下の特徴を持っています:
- 静的型付け: コンパイル時にエラーを検出
- ES6+サポート: 最新のJavaScript機能をサポート
- 優れたIDE支援: IntelliSenseとリファクタリング機能
- 段階的導入: 既存のJavaScriptプロジェクトに段階的に導入可能
基本的な型システム
プリミティブ型
// 基本的な型
let name: string = "田中";
let age: number = 25;
let isStudent: boolean = true;
let nothing: null = null;
let notDefined: undefined = undefined;
// 配列
let numbers: number[] = [1, 2, 3, 4, 5];
let names: Array<string> = ["田中", "佐藤", "鈴木"];
// タプル
let person: [string, number] = ["田中", 25];
オブジェクト型
// インターフェース
// 使用例
const user: User = {
id: 1,
name: "田中太郎",
email: "tanaka@example.com",
createdAt: new Date()
};
// 型エイリアス
type Status = "pending" | "approved" | "rejected";
type UserWithStatus = User & { status: Status };
関数型
// 関数の型定義
function greet(name: string): string {
return `こんにちは、${name}さん!`;
}
// アロー関数
const add = (a: number, b: number): number => a + b;
// オプショナルパラメータ
function createUser(name: string, age?: number): User {
return {
id: Math.random(),
name,
email: `${name}@example.com`,
age,
createdAt: new Date()
};
}
// デフォルトパラメータ
function multiply(a: number, b: number = 1): number {
return a * b;
}
// レストパラメータ
function sum(...numbers: number[]): number {
return numbers.reduce((total, num) => total + num, 0);
}
高度な型機能
ジェネリクス
// ジェネリック関数
function identity<T>(arg: T): T {
return arg;
}
const stringResult = identity<string>("hello");
const numberResult = identity<number>(42);
// ジェネリックインターフェース
const userResponse: ApiResponse<UserData> = {
data: { id: 1, name: "田中" },
status: 200,
message: "成功"
};
// ジェネリック制約
function logLength<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
logLength("hello"); // OK
logLength([1, 2, 3]); // OK
// logLength(123); // エラー:numberにはlengthプロパティがない
ユニオン型と交差型
// ユニオン型
type StringOrNumber = string | number;
function formatValue(value: StringOrNumber): string {
if (typeof value === "string") {
return value.toUpperCase();
} else {
return value.toString();
}
}
// 交差型
type PersonEmployee = Person & Employee;
const employee: PersonEmployee = {
name: "田中",
age: 30,
employeeId: "EMP001",
department: "開発部"
};
条件付き型とマップ型
// 条件付き型
type NonNullable<T> = T extends null | undefined ? never : T;
type StringType = NonNullable<string | null>; // string
type NumberType = NonNullable<number | undefined>; // number
// マップ型
type Partial<T> = {
[P in keyof T]?: T[P];
};
type Required<T> = {
[P in keyof T]-?: T[P];
};
type PartialUser = Partial<User>; // すべてのプロパティがオプショナル
type RequiredUser = Required<User>; // すべてのプロパティが必須
実践的な使用例
React with TypeScript
const ContactForm: React.FC<Props> = ({ title, onSubmit }) => {
const [formData, setFormData] = useState<FormData>({
name: '',
email: '',
message: ''
});
const [errors, setErrors] = useState<Partial<FormData>>({});
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
// バリデーション
const newErrors: Partial<FormData> = {};
if (!formData.name) newErrors.name = '名前は必須です';
if (!formData.email) newErrors.email = 'メールアドレスは必須です';
if (Object.keys(newErrors).length > 0) {
setErrors(newErrors);
return;
}
onSubmit(formData);
};
return (
<form onSubmit={handleSubmit}>
<h2>{title}</h2>
<div>
<input
type="text"
placeholder="名前"
value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
/>
{errors.name && <span className="error">{errors.name}</span>}
</div>
<div>
<input
type="email"
placeholder="メールアドレス"
value={formData.email}
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
/>
{errors.email && <span className="error">{errors.email}</span>}
</div>
<div>
<textarea
placeholder="メッセージ"
value={formData.message}
onChange={(e) => setFormData({ ...formData, message: e.target.value })}
/>
</div>
<button type="submit">送信</button>
</form>
);
};
export default ContactForm;
API クライアント
// API レスポンスの型定義
// APIクライアントクラス
class ApiClient {
private baseUrl: string;
constructor(baseUrl: string) {
this.baseUrl = baseUrl;
}
async get<T>(endpoint: string): Promise<ApiResponse<T>> {
try {
const response = await fetch(`${this.baseUrl}${endpoint}`);
const data = await response.json();
if (!response.ok) {
return {
success: false,
error: {
code: response.status.toString(),
message: data.message || 'エラーが発生しました'
}
};
}
return {
success: true,
data
};
} catch (error) {
return {
success: false,
error: {
code: 'NETWORK_ERROR',
message: 'ネットワークエラーが発生しました'
}
};
}
}
async post<T, U>(endpoint: string, body: T): Promise<ApiResponse<U>> {
try {
const response = await fetch(`${this.baseUrl}${endpoint}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
});
const data = await response.json();
if (!response.ok) {
return {
success: false,
error: {
code: response.status.toString(),
message: data.message || 'エラーが発生しました'
}
};
}
return {
success: true,
data
};
} catch (error) {
return {
success: false,
error: {
code: 'NETWORK_ERROR',
message: 'ネットワークエラーが発生しました'
}
};
}
}
}
// 使用例
const apiClient = new ApiClient('https://api.example.com');
async function fetchUser(id: number): Promise<User | null> {
const response = await apiClient.get<User>(`/users/${id}`);
if (response.success && response.data) {
return response.data;
} else {
console.error('ユーザーの取得に失敗しました:', response.error?.message);
return null;
}
}
移行のベストプラクティス
段階的な移行
- tsconfig.jsonの設定
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"lib": ["ES2020", "DOM"],
"allowJs": true,
"checkJs": false,
"strict": false,
"noImplicitAny": false,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
- ファイルの段階的な変換
.js→.tsに拡張子を変更- 型エラーを一つずつ修正
any型から具体的な型に変更
- 厳密性の段階的な向上
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true
}
}
よくある問題と解決策
// 問題:any型の乱用
function processData(data: any): any {
return data.someProperty;
}
// 解決:適切な型定義
function processData(data: DataType): string {
return data.someProperty;
}
// 問題:null/undefinedの処理
function getName(user: User): string {
return user.name.toUpperCase(); // user.nameがnullの可能性
}
// 解決:null チェック
function getName(user: User): string {
return user.name?.toUpperCase() ?? '名前なし';
}
まとめ
TypeScriptを導入することで:
- コンパイル時エラー検出: バグを早期に発見
- 優れたIDE支援: 自動補完とリファクタリング
- コードの自己文書化: 型が仕様書の役割
- チーム開発の効率化: 型による契約の明確化
段階的に導入し、チーム全体でTypeScriptの恩恵を享受しましょう!
頑張って! 🚀