はじめに
結論から言うと、TypeScriptは2024年以降のJavaScript開発では必須スキルになっています。
GitHubで107,000スター以上、毎月3億5000万回以上のnpmダウンロード。これはもう「流行り」とかじゃなくて、完全にデファクトスタンダードになったということですね。
正直なところ、最初は「型を書くのめんどくさそう」「JavaScriptで十分じゃない?」と思っていたんですよ。でも実際に使い始めたら、もう戻れなくなりました。30代になって思うのは、こういう「最初の学習コスト」を惜しんでいると、後で大きなツケを払うことになるということ。
TypeScriptとは
TypeScriptはMicrosoftが開発した、JavaScriptに静的型付けを追加したプログラミング言語です。「JavaScriptのスーパーセット」と呼ばれていて、既存のJavaScriptコードはそのままTypeScriptとして動作します。
現在の最新バージョンはv5.9.3で、Apache License 2.0のオープンソースとして公開されています。11年以上の歴史があり、812人以上のコントリビューターが開発に参加しています。
特徴・メリット
1. コンパイル時にエラーを検出できる
これ、意外と軽視されがちなんですけど、実行前にエラーが分かるというのは本当に大きいんですよ。
JavaScriptだと、typoしたプロパティ名とか、nullの可能性があるオブジェクトへのアクセスとか、実行するまで気づかない。TypeScriptなら、エディタ上で即座に赤線が引かれます。
個人的には、この「実行前に問題が分かる」というのが一番のメリットだと思います。
2. IDEの補完が強力になる
TypeScriptを導入すると、VSCodeの補完が劇的に賢くなります。
オブジェクトのプロパティ、関数の引数、戻り値...全部補完してくれる。ドキュメントを見に行く回数が激減しました。時短になる。
3. リファクタリングが安全にできる
大規模プロジェクトで関数の引数を変更したいとき、JavaScriptだと「どこで使われているか」を目視で確認するしかない。
TypeScriptなら、型が合わなくなった箇所が全部エラーになります。これ、QOL上がりますよ。
4. 型がドキュメント代わりになる
コードを読むとき、関数の引数や戻り値の型が分かるだけで理解度が全然違います。
「この関数は何を受け取って何を返すの?」がコードを見ただけで分かる。コメントやドキュメントが陳腐化する問題も解決します。
5. JavaScriptと完全互換
既存のJavaScriptコードは全て有効なTypeScriptコードです。段階的に型を追加していけるので、移行のハードルが低い。
インストール方法
前提条件
Node.jsがインストールされている必要があります。
グローバルインストール
npm install -g typescript
これでtscコマンドが使えるようになります。
プロジェクトへのインストール
npm install -D typescript
プロジェクトローカルにインストールする方が一般的ですね。
初期設定
npx tsc --init
これでtsconfig.jsonが生成されます。
基本的な使い方
型アノテーション
変数や関数に型を指定する基本的な書き方です。
// 変数の型
let name: string = "Takuma";
let age: number = 35;
let isActive: boolean = true;
// 配列の型
let numbers: number[] = [1, 2, 3];
let names: Array<string> = ["Alice", "Bob"];
// 関数の型
function greet(name: string): string {
return `Hello, ${name}!`;
}
// アロー関数
const add = (a: number, b: number): number => a + b;
型推論
TypeScriptは賢いので、明示的に型を書かなくても推論してくれます。
// 型推論により string と判定される
let message = "Hello";
// 型推論により number[] と判定される
let scores = [85, 90, 78];
// 戻り値も推論される
const multiply = (a: number, b: number) => a * b; // 戻り値は number
全部に型を書く必要はないんですよ。これ、最初知らなかった。
インターフェース
オブジェクトの形を定義するのに使います。
interface User {
id: number;
name: string;
email: string;
age?: number; // オプショナル
}
const user: User = {
id: 1,
name: "Takuma",
email: "takuma@example.com",
};
function getUserName(user: User): string {
return user.name;
}
型エイリアス
インターフェースと似ていますが、より柔軟に使えます。
type Status = "pending" | "approved" | "rejected";
type Point = {
x: number;
y: number;
};
type ID = string | number;
ジェネリクス
再利用可能な型を作るときに使います。
function identity<T>(arg: T): T {
return arg;
}
const num = identity<number>(42);
const str = identity<string>("hello");
// 配列を扱う汎用関数
function getFirst<T>(arr: T[]): T | undefined {
return arr[0];
}
tsconfig.json
よく使う設定はこんな感じ。
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"outDir": "./dist",
"rootDir": "./src",
"declaration": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
strict: trueは必須ですね。これを外すとTypeScriptの恩恵が半減します。
実践的なユースケース
React + TypeScript
interface ButtonProps {
label: string;
onClick: () => void;
disabled?: boolean;
variant?: "primary" | "secondary";
}
const Button: React.FC<ButtonProps> = ({
label,
onClick,
disabled = false,
variant = "primary",
}) => {
return (
<button
className={`btn btn-${variant}`}
onClick={onClick}
disabled={disabled}
>
{label}
</button>
);
};
propsの型が明確になるので、コンポーネントの使い方で迷わない。
APIレスポンスの型定義
interface ApiResponse<T> {
data: T;
status: number;
message: string;
}
interface Product {
id: number;
name: string;
price: number;
category: string;
}
async function fetchProducts(): Promise<ApiResponse<Product[]>> {
const response = await fetch("/api/products");
return response.json();
}
// 使用時に型が効く
const result = await fetchProducts();
result.data.forEach((product) => {
console.log(product.name); // 補完が効く
});
Zodとの組み合わせ
実行時のバリデーションと型を同時に定義できます。
import { z } from "zod";
const UserSchema = z.object({
name: z.string().min(1),
email: z.string().email(),
age: z.number().min(0).optional(),
});
type User = z.infer<typeof UserSchema>;
function createUser(input: unknown): User {
return UserSchema.parse(input);
}
これ、APIのバリデーションで使うと便利ですよ。
Express + TypeScript
import express, { Request, Response } from "express";
interface CreateUserBody {
name: string;
email: string;
}
const app = express();
app.post(
"/users",
(req: Request<{}, {}, CreateUserBody>, res: Response) => {
const { name, email } = req.body;
// name と email の型が保証される
res.json({ id: 1, name, email });
}
);
JavaScriptからの移行
コスパ的に、JavaScriptからの移行は十分価値があります。
段階的に移行するポイント:
-
まずtsconfig.jsonを追加
allowJs: trueで.jsファイルもそのまま使える
-
ファイルを1つずつ.tsに変更
- 重要なファイルから着手
-
any型から始めてもOK
- 最初は
anyでも動く。徐々に具体的な型に置き換える
- 最初は
-
strictモードは最後に有効化
- 最初は緩い設定で、慣れてきたら厳しくする
{
"compilerOptions": {
"allowJs": true,
"checkJs": false,
"strict": false // 最初は緩く
}
}
まとめ
TypeScriptを導入して感じた変化:
- バグ発見: 実行前に見つかるようになった
- 開発速度: IDEの補完で体感2倍速くなった
- リファクタリング: 安心して変更できるようになった
- コードの可読性: 型がドキュメント代わりになる
正直なところ、新規プロジェクトでTypeScriptを使わない理由はもうほとんどないと思います。学習コストは確かにありますが、それを上回るリターンがあります。TypeScript一択ですね。
特にReactやVue、Node.jsとの相性が良くて、エコシステムも充実している。フロントエンドもバックエンドも、TypeScriptで統一できるのは大きいです。
まだ試していない人は、まず小さなプロジェクトで使ってみてください。戻れなくなりますよ。