TypeScriptを書き始めたばかりの頃、誰もが一度はぶつかる壁が「型の書き方が分からない」「anyだらけになってしまう」という問題だ。本記事は、20〜40代の現役Webエンジニアを対象に、TypeScript 5.x時代における型の基礎を完全網羅する。プリミティブ・配列・オブジェクト・タプル・ユニオン・リテラル・ジェネリクス入門まで、コピペで動くサンプルコードを40個以上掲載し、JavaScriptからの移行・any地獄からの脱出・型安全なリファクタリングの実例を「Before / After」形式で徹底解説する。
既存の「TypeScript完全実践ガイド」が総合カタログだとすれば、本記事は型の基礎に集中したHub記事として位置づける。読み終える頃には、型注釈の必要な箇所・不要な箇所を判断でき、ユニオン型でnarrowingを書けるレベルになる。
- 環境準備:tsconfig.jsonの推奨設定
- プリミティブ型:7種類を完全把握する
- 配列とタプル:似て非なる型
- オブジェクト型:typeとinterfaceの使い分け
- ユニオン型・リテラル型・narrowing
- enum vs as const:なぜ as const 推奨か
- typeof と keyof:型から型を作る基本
- 関数の型:シグネチャ・オーバーロード・thisまで
- any / unknown / never / void:似て非なる4型
- as const と satisfies:TS 4.9以降の必修
- import type と verbatimModuleSyntax
- 実践:any地獄からの脱出リファクタリング
- 型注釈は「どこに書くか」が9割
- React + TypeScript の最小サンプル
- よくあるエラーメッセージと解消法
- FAQ
- まとめ
環境準備:tsconfig.jsonの推奨設定
型の話に入る前に、TypeScript 5.x 推奨の tsconfig.json を確認しておく。strict: true が無効だと、本記事の例の半分は警告すら出ない。
最小推奨tsconfig
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noImplicitOverride": true,
"noFallthroughCasesInSwitch": true,
"isolatedModules": true,
"verbatimModuleSyntax": true,
"skipLibCheck": true,
"esModuleInterop": true
},
"include": ["src/**/*"]
}
strict配下に含まれるフラグの内訳
| フラグ | 役割 | 外すとどうなるか |
|---|---|---|
noImplicitAny | 暗黙的anyを禁止 | 型を書き忘れた箇所が全てanyに |
strictNullChecks | null/undefinedを別物として扱う | nullアクセス事故が型でなくランタイムで露呈 |
strictFunctionTypes | 関数引数を反変として扱う | callback型の代入が緩くなる |
strictBindCallApply | bind/call/applyを型検査 | 引数ミスマッチが通る |
alwaysStrict | “use strict”自動付与 | 暗黙的グローバル化リスク |
noUncheckedIndexedAccessは入れる派
// noUncheckedIndexedAccess: true const arr = ["a", "b", "c"]; const first = arr[0]; // ^? const first: string | undefined // false の場合は string になる(範囲外アクセスが型で見えない)
このフラグはTypeScriptの「嘘をつかない型」運用の入り口だ。配列の添字アクセスは厳密にはT | undefinedだから、それを型に反映してくれる。
プリミティブ型:7種類を完全把握する
TypeScriptのプリミティブ型は string / number / boolean / null / undefined / bigint / symbol の7種類。StringやNumberとは別物なので注意。
string / number / boolean
let userName: string = "Taro";
let age: number = 30;
let isActive: boolean = true;
// テンプレートリテラルもstring
const greet: string = `Hello, ${userName}`;
// 16進・2進・指数表記すべてnumber
const hex: number = 0xff;
const bin: number = 0b1010;
const exp: number = 1.5e3;
大文字始まりの型は使わない
// Before(ダメ) let name: String = "Taro"; // String型(オブジェクトラッパー) let age: Number = 30; // Number型 // After(正しい) let userName: string = "Taro"; let userAge: number = 30; // String型はメソッドが効かない箇所がある const s: String = "hello"; // s.toUpperCase() は動くが、関数引数で string を要求されると渡せない
null と undefined の使い分け
// undefined: 値が「まだ無い」
let assigned: string | undefined;
assigned = "later";
// null: 値が「明示的に無い」
type ApiUser = {
id: number;
nickname: string | null; // DBのNULLを表現
};
// strictNullChecksがONなら、両者は別の型
function f(x: string) { /* ... */ }
let n: null = null;
// f(n); // Error: 'null' is not assignable to 'string'
bigintとsymbol
// bigint: 任意精度整数(末尾n)
const big: bigint = 9007199254740993n;
const sum: bigint = big + 1n; // numberと混ぜられない
// symbol: 一意な識別子
const SECRET: symbol = Symbol("secret");
const obj = { [SECRET]: "value" };
配列とタプル:似て非なる型
配列の2種類の書き方
// 短縮形(推奨) const names: string[] = ["Alice", "Bob"]; // ジェネリック形(混在型で読みやすい) const ids: Array<number | string> = [1, "u_2", 3]; // readonly配列(push/popできない) const nums: readonly number[] = [1, 2, 3]; // nums.push(4); // Error
タプル(固定長・各要素別の型)
// タプル type Point = [number, number]; const p: Point = [10, 20]; // ラベル付きタプル(TS 4.0以降) type RGB = [r: number, g: number, b: number]; const red: RGB = [255, 0, 0]; // React useState 風の戻り値 type UseToggle = [value: boolean, toggle: () => void]; // 可変長タプル(rest) type WithFirst<T> = [first: T, ...rest: T[]]; const list: WithFirst<string> = ["head", "tail1", "tail2"];
readonly tupleとas const
// readonly tuple
const point: readonly [number, number] = [1, 2];
// as const で全要素 literal & readonly
const route = ["GET", "/users", 200] as const;
// ^? readonly ["GET", "/users", 200]
// 関数の戻り値を as const にすると Hooks の型推論が綺麗
function useToggle(initial = false) {
const [v, setV] = useState(initial);
const toggle = () => setV(prev => !prev);
return [v, toggle] as const; // [boolean, () => void] に推論
}
オブジェクト型:typeとinterfaceの使い分け
type alias による定義
type User = {
id: number;
name: string;
email: string;
isActive: boolean;
};
const u: User = {
id: 1,
name: "Alice",
email: "alice@example.com",
isActive: true,
};
interface による定義
interface User {
id: number;
name: string;
email: string;
}
// 同名で拡張(declaration merging)
interface User {
isActive: boolean;
}
// 結果:User = { id; name; email; isActive }
const u: User = { id: 1, name: "Alice", email: "x", isActive: true };
使い分けの判断基準
| 状況 | 推奨 | 理由 |
|---|---|---|
| 外部APIレスポンス型 | type | ユニオン・交差・mapped typeが書きやすい |
| クラスのcontract | interface | implementsで使う前提 |
| 拡張ポイント(ライブラリ作者) | interface | declaration mergingでユーザーが拡張可能 |
| ユニオン型を含む | type | interfaceはユニオンを直接持てない |
| 関数シグネチャ集合 | どちらでも可 | readability優先 |
オプショナル ・readonly・index signature
type Article = {
id: number;
title: string;
subtitle?: string; // オプショナル
readonly slug: string; // 書き換え不可
[meta: string]: unknown; // index signature
};
const a: Article = {
id: 1,
title: "Hello",
slug: "hello",
publishedAt: "2026-05-26",
};
// a.slug = "x"; // Error: Cannot assign to 'slug'
interface の extends
interface Animal {
name: string;
}
interface Dog extends Animal {
breed: string;
}
// 複数継承も可能
interface Timestamps {
createdAt: Date;
updatedAt: Date;
}
interface Post extends Timestamps {
id: number;
title: string;
}
type の intersection
type WithId = { id: number };
type WithName = { name: string };
// 交差型(intersection)
type Entity = WithId & WithName;
const e: Entity = { id: 1, name: "Alice" };
// 矛盾するプロパティはnever
type A = { kind: "a" };
type B = { kind: "b" };
type AB = A & B;
// ^? { kind: never } 事実上使えない
ユニオン型・リテラル型・narrowing
ユニオン型の基本
// 値の取り得る型を | で並べる
type ID = number | string;
const a: ID = 1;
const b: ID = "user_1";
function format(id: ID): string {
// id.toUpperCase(); // Error: numberにはtoUpperCaseがない
return String(id); // 共通メソッドのみ使える
}
リテラル型(値そのものを型に)
// 文字列リテラル型
type Direction = "north" | "south" | "east" | "west";
function move(dir: Direction) {
// ...
}
move("north"); // OK
// move("up"); // Error
// 数値リテラル型
type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6;
// boolean リテラル型
type LoadingState = { loading: true } | { loading: false; data: string };
typeofによるnarrowing
function format(value: string | number): string {
if (typeof value === "string") {
return value.toUpperCase(); // ここでは string に絞り込まれる
}
// ここでは number
return value.toFixed(2);
}
in 演算子によるnarrowing
type Cat = { name: string; purr: () => void };
type Dog = { name: string; bark: () => void };
function speak(pet: Cat | Dog) {
if ("purr" in pet) {
pet.purr(); // Cat に絞り込まれる
} else {
pet.bark(); // Dog
}
}
instanceof によるnarrowing
class AppError extends Error {
constructor(public code: number, message: string) {
super(message);
}
}
function handle(e: unknown) {
if (e instanceof AppError) {
console.error(`[${e.code}] ${e.message}`);
} else if (e instanceof Error) {
console.error(e.message);
} else {
console.error("Unknown error", e);
}
}
discriminated union(判別ユニオン)
// 共通の判別子 kind を持たせる
type Result<T> =
| { kind: "ok"; value: T }
| { kind: "error"; message: string };
function unwrap<T>(r: Result<T>): T {
if (r.kind === "ok") {
return r.value; // T
}
throw new Error(r.message); // string
}
// React の fetch ステータスにも応用
type FetchState<T> =
| { status: "idle" }
| { status: "loading" }
| { status: "success"; data: T }
| { status: "error"; error: Error };
網羅性チェック:never型の活用
type Status = "idle" | "loading" | "success" | "error";
function describe(s: Status): string {
switch (s) {
case "idle": return "待機中";
case "loading": return "読込中";
case "success": return "成功";
case "error": return "失敗";
default: {
const _exhaustive: never = s; // ← ここでケース漏れがコンパイルエラー
return _exhaustive;
}
}
}
// Statusに "cancel" を追加すると、default節でコンパイルエラーが出る
enum vs as const:なぜ as const 推奨か
従来のenum(非推奨寄り)
enum Role {
Admin,
Editor,
Viewer,
}
// 内部値が 0,1,2(意図しない数値リーク)
console.log(Role.Admin); // 0
// string enum なら値は明示
enum Status {
Idle = "idle",
Loading = "loading",
}
as const オブジェクト方式(推奨)
// Before
enum Role { Admin, Editor, Viewer }
// After
const Role = {
Admin: "admin",
Editor: "editor",
Viewer: "viewer",
} as const;
type Role = typeof Role[keyof typeof Role];
// ^? "admin" | "editor" | "viewer"
function setRole(r: Role) { /* ... */ }
setRole(Role.Admin);
setRole("editor"); // 文字列リテラルでも渡せる(柔軟)
enum vs as const 比較表
| 項目 | enum | as const オブジェクト |
|---|---|---|
| ランタイム出力 | 独自オブジェクトを生成 | そのままJSオブジェクト |
| Tree-shaking | const enum以外は効きにくい | 効く |
| 文字列リテラル代入 | 不可 | 可 |
| declaration merging | あり | なし |
| –isolatedModules相性 | const enumと相性悪い | 無問題 |
| 初学者の理解 | 独自概念で混乱しがち | JSの延長で理解しやすい |
typeof と keyof:型から型を作る基本
typeof で値から型を取り出す
const config = {
apiUrl: "https://api.example.com",
timeout: 5000,
retry: 3,
};
type Config = typeof config;
// ^? { apiUrl: string; timeout: number; retry: number }
// JSON設定ファイルを単一の真実の源(SSOT)にできる
keyof でプロパティ名のユニオンを取り出す
type User = {
id: number;
name: string;
email: string;
};
type UserKey = keyof User;
// ^? "id" | "name" | "email"
function getProp<K extends keyof User>(u: User, key: K): User[K] {
return u[key];
}
const u: User = { id: 1, name: "Alice", email: "x" };
const id = getProp(u, "id"); // number
const name = getProp(u, "name"); // string
// const x = getProp(u, "age"); // Error
typeof + keyof の合わせ技
const ROUTES = {
home: "/",
posts: "/posts",
postDetail: "/posts/:id",
about: "/about",
} as const;
type RouteName = keyof typeof ROUTES;
// ^? "home" | "posts" | "postDetail" | "about"
type RoutePath = typeof ROUTES[RouteName];
// ^? "/" | "/posts" | "/posts/:id" | "/about"
function go(name: RouteName) {
window.location.href = ROUTES[name];
}
関数の型:シグネチャ・オーバーロード・thisまで
関数シグネチャの書き方
// 関数式に型注釈
const add: (a: number, b: number) => number = (a, b) => a + b;
// type alias で再利用
type BinaryOp = (a: number, b: number) => number;
const sub: BinaryOp = (a, b) => a - b;
// interface でも関数型を表現可能
interface Reducer<S, A> {
(state: S, action: A): S;
}
オプショナル引数・デフォルト引数・rest引数
function greet(name: string, prefix?: string): string {
return `${prefix ?? "Mr."} ${name}`;
}
function withDefault(timeout: number = 5000): void {
// timeout は number
}
function sum(...nums: number[]): number {
return nums.reduce((a, b) => a + b, 0);
}
sum(1, 2, 3); // 6
関数オーバーロード
// 戻り値が引数の型に依存するケース
function parse(input: string): string[];
function parse(input: number): number[];
function parse(input: string | number): string[] | number[] {
if (typeof input === "string") {
return input.split(",");
}
return Array.from(String(input), Number);
}
const a = parse("a,b,c"); // string[]
const b = parse(12345); // number[]
thisの型注釈
interface Component {
name: string;
render(this: Component): string;
}
const c: Component = {
name: "Card",
render() {
return `<div>${this.name}</div>`; // this は Component
},
};
戻り値の型推論 vs 明示
// 推論に任せる(短いユーティリティ)
const double = (n: number) => n * 2; // 戻り値推論: number
// 明示する(API境界・ライブラリ・公開関数)
export function fetchUser(id: number): Promise<User> {
return fetch(`/api/users/${id}`).then(r => r.json());
}
// 明示するメリット:
// - 実装ミスで意図しない戻り値型になっても即エラー
// - エディタのホバーで型が読みやすい
// - 公開APIの破壊的変更検出
any / unknown / never / void:似て非なる4型
any:型チェックを完全に切る最終手段
// Before(anyの濫用)
function process(data: any) {
return data.user.name.toUpperCase();
// ↑ data.user が undefined でもコンパイル通る → 実行時クラッシュ
}
// After(unknown + 型ガード)
function processSafe(data: unknown): string {
if (
typeof data === "object" &&
data !== null &&
"user" in data &&
typeof (data as any).user?.name === "string"
) {
return ((data as { user: { name: string } }).user.name).toUpperCase();
}
throw new Error("Invalid data");
}
unknown:any の安全な代替
// JSON.parse の戻り値は unknown として扱うのがベスト
function safeParse(json: string): unknown {
return JSON.parse(json);
}
const data = safeParse('{"name":"Alice"}');
// data.name; // Error: 'data' is of type 'unknown'
// 型ガードで絞り込み
function isUser(x: unknown): x is { name: string } {
return typeof x === "object" && x !== null && "name" in x &&
typeof (x as { name: unknown }).name === "string";
}
if (isUser(data)) {
console.log(data.name); // string
}
never:値が存在しない型
// 関数が決してreturnしない場合の戻り値
function panic(msg: string): never {
throw new Error(msg);
}
// 無限ループ
function loop(): never {
while (true) { /* ... */ }
}
// 網羅性チェックでも使う(前出の switch default)
type Status = "ok" | "ng";
function f(s: Status) {
switch (s) {
case "ok": return;
case "ng": return;
default: const _: never = s;
}
}
void:戻り値を使わない宣言
function log(msg: string): void {
console.log(msg);
}
// void 戻り値の特殊ルール:呼び出し側で何か返してもOK
const handlers: Array<() => void> = [];
handlers.push(() => 123); // OK(戻り値を無視する契約)
// undefined を返すと明示するなら undefined 型
function maybeReturn(): undefined {
return;
}
4型の比較表
| 型 | 意味 | 代入可能(他型→当該型) | 取り出し時 | 使いどころ |
|---|---|---|---|---|
any | 型検査を放棄 | すべて可 | すべて可 | 移行期の一時退避のみ |
unknown | 未知の型 | すべて可 | narrowing必須 | 外部入力の入口 |
never | 到達不能 | 不可 | 使えない | 網羅性・throw専用関数 |
void | 戻り値無視 | undefinedのみ | 使えない | callback戻り値 |
as const と satisfies:TS 4.9以降の必修
as const アサーションのおさらい
// without as const
const colors = ["red", "blue"];
// ^? string[]
// with as const
const colors2 = ["red", "blue"] as const;
// ^? readonly ["red", "blue"]
// オブジェクトでも
const theme = {
primary: "#0070f3",
secondary: "#f00",
} as const;
// theme.primary は "#0070f3" リテラル型
satisfies 演算子(TS 4.9+)の意義
type Config = Record<string, string | string[]>;
// Before(型注釈で潰れる)
const config1: Config = {
endpoint: "https://api.example.com",
tags: ["a", "b"],
};
// config1.tags.map(...) で .map は推論できるが、
// config1.endpoint は string で固定。リテラル情報が失われる
// After(satisfies で型チェックしつつリテラルを保つ)
const config2 = {
endpoint: "https://api.example.com",
tags: ["a", "b"],
} satisfies Config;
config2.endpoint; // "https://api.example.com" (リテラル)
config2.tags; // string[] (推論)
satisfies の現場ユースケース
// ルート定義を型チェックしつつ補完を効かせる
type Route = { path: string; auth: boolean };
const routes = {
home: { path: "/", auth: false },
admin: { path: "/admin", auth: true },
} satisfies Record<string, Route>;
// routes.home.path は "/" のリテラル
// routes.admin.auth は true のリテラル
// 設定漏れもエラーで気付ける
// const broken = { home: { path: "/" } } satisfies Record<string, Route>;
// → 'auth' プロパティが欠落しています
import type と verbatimModuleSyntax
型のみインポート
// 値と型が混在
import { fetchUser, User } from "./user";
// 推奨:型は import type で
import { fetchUser } from "./user";
import type { User } from "./user";
// あるいは inline 構文
import { fetchUser, type User } from "./user";
verbatimModuleSyntax の意味
// tsconfig: "verbatimModuleSyntax": true
// → import 文がそのまま出力に残るかどうかが「書いた通り」になる
// → 型だけのimportは必ず import type と明示しないと、
// 未使用バンドルの混入や副作用呼び出し有無が変わるリスクを排除
// Bad
import { User } from "./types"; // 値として残るか型として消えるか曖昧
// Good
import type { User } from "./types"; // 必ず削除される
export type
// 関数(値)と型を区別してエクスポート
export function createUser(name: string): User {
return { id: Date.now(), name };
}
export type User = {
id: number;
name: string;
};
// あるいは再エクスポート
export type { User } from "./types";
実践:any地獄からの脱出リファクタリング
Before:JavaScriptそのまま移植
// Before:全部 any、型の利益ゼロ
function fetchPosts(opts: any): any {
return fetch(opts.url)
.then((res: any) => res.json())
.then((data: any) => {
return data.posts.filter((p: any) => p.published);
});
}
const result = fetchPosts({ url: "/api/posts" });
// result.then(posts => posts[0].titl) // typo もすり抜ける
After:型を入れて安全に
type FetchPostsOptions = {
url: string;
signal?: AbortSignal;
};
type Post = {
id: number;
title: string;
body: string;
published: boolean;
};
type PostsResponse = {
posts: Post[];
total: number;
};
async function fetchPosts(opts: FetchPostsOptions): Promise<Post[]> {
const res = await fetch(opts.url, { signal: opts.signal });
if (!res.ok) {
throw new Error(`HTTP ${res.status}`);
}
const data = (await res.json()) as PostsResponse;
return data.posts.filter(p => p.published);
}
const posts = await fetchPosts({ url: "/api/posts" });
posts[0]?.title; // string(typoはエディタが補完で防ぐ)
段階移行のコツ
- まず
tsconfig.jsonでstrictをON、noImplicitAny違反だけを潰す - 外部APIレスポンスは
unknownで受けて型ガードを書く - 共通の型は
src/types/*.tsに集約し、命名はUser/Postなど短く anyを使うときは// TODO(typing): ...でgrepしやすくしておく- ESLintルール
@typescript-eslint/no-explicit-anyをwarnでCIに乗せる
型注釈は「どこに書くか」が9割
書くべき箇所:境界線(API・関数の入口出口)
// 良い:公開関数の引数・戻り値は明示
export function calcTax(price: number, rate: number): number {
return Math.floor(price * rate);
}
// 良い:外部から受け取る型は明示
type FormValues = {
name: string;
email: string;
age: number;
};
export function submit(values: FormValues): Promise<void> {
return fetch("/api/submit", { method: "POST", body: JSON.stringify(values) })
.then(() => undefined);
}
書かなくていい箇所:ローカル変数
// Bad(冗長) const count: number = 0; const message: string = "Hello"; const items: string[] = ["a", "b"]; // Good(推論に任せる) const count = 0; const message = "Hello"; const items = ["a", "b"];
判断フロー
- 公開API(他モジュール・ライブラリ・外部依存)→ 明示
- ローカル変数の初期化で型が一意に決まる → 推論
- 初期値が空で後から代入(
let arr = [])→ 明示(let arr: string[] = []) - JSON.parse・外部fetchの結果 → unknownで受けて型ガード
- 戻り値が複雑なジェネリクスを含む → 明示(ホバー時の可読性)
React + TypeScript の最小サンプル
Props の型定義
type ButtonProps = {
label: string;
variant?: "primary" | "secondary" | "danger";
disabled?: boolean;
onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void;
};
export function Button({
label,
variant = "primary",
disabled = false,
onClick,
}: ButtonProps) {
return (
<button
className={`btn btn-${variant}`}
disabled={disabled}
onClick={onClick}
>
{label}
</button>
);
}
useState の型推論と明示
// 初期値から推論
const [count, setCount] = useState(0); // number
const [name, setName] = useState("Alice"); // string
// nullが入り得るなら明示
const [user, setUser] = useState<User | null>(null);
// 配列・オブジェクトも
const [items, setItems] = useState<Item[]>([]);
useReducer + discriminated union
type State = { count: number };
type Action =
| { type: "increment" }
| { type: "decrement" }
| { type: "set"; payload: number };
function reducer(state: State, action: Action): State {
switch (action.type) {
case "increment": return { count: state.count + 1 };
case "decrement": return { count: state.count - 1 };
case "set": return { count: action.payload };
default: {
const _: never = action;
return state;
}
}
}
const [state, dispatch] = useReducer(reducer, { count: 0 });
dispatch({ type: "set", payload: 100 }); // OK
// dispatch({ type: "set" }); // Error: payload required
よくあるエラーメッセージと解消法
Object is possibly ‘undefined’
const items: string[] = ["a", "b"]; const first = items[0]; // noUncheckedIndexedAccess: true なら string | undefined // オプショナルチェイニング first?.toUpperCase(); // 早期return if (first === undefined) return; first.toUpperCase(); // ここでは string // non-null assertion(本当に確実なときだけ) items[0]!.toUpperCase();
Argument of type ‘X’ is not assignable to parameter of type ‘Y’
type Color = "red" | "blue";
function paint(c: Color) { /* ... */ }
const userInput = "red"; // string と推論される
// paint(userInput); // Error
// 解決1: as const で literal にする
const userInput2 = "red" as const;
paint(userInput2); // OK
// 解決2: 型を明示
const userInput3: Color = "red";
paint(userInput3); // OK
Type ‘string’ is not assignable to type ‘never’
// 空配列の型が never[] になっている
const arr = []; // any[](strictなら never[] に近い挙動)
// arr.push("a"); // 環境によりエラー
// 解決:初期化時に型を与える
const arr2: string[] = [];
arr2.push("a"); // OK
FAQ
Q. JavaScriptしか書いたことがありません。TypeScriptは難しいですか?
A. 文法はほぼ同じです。差は「変数・引数・戻り値に型を書く」だけ。本記事のプリミティブ型→配列→オブジェクト→ユニオン型の順で進めれば、3日で実務レベルの記述ができるようになります。ジェネリクスやmapped typeは慣れてから学べばOKです。
Q. type と interface はどちらを使えばいいですか?
A. ユニオン型・交差型・mapped typeを使うならtype、クラスとペアで使う・ライブラリの拡張点として公開するならinterfaceがおすすめです。チームで統一されているなら、その方針に従うのが最優先です。
Q. any を絶対に使ってはいけませんか?
A. 段階移行中の一時退避としては許容します。ただし、コメントで // TODO(typing) を残し、ESLintでwarnを出してCIで件数を可視化しましょう。新規コードでは原則unknownを使うべきです。
Q. enum は使うべきですか?
A. 新規プロジェクトではas constオブジェクト方式を推奨します。ツリーシェイキング・--isolatedModulesとの相性・初学者の理解しやすさの3点で優位です。既存のenumを無理に書き換える必要はありません。
Q. tsconfigで何を最初にONにすべきですか?
A. strict: trueとnoUncheckedIndexedAccess: trueの2つです。前者でnoImplicitAny・strictNullChecks等の主要フラグが一気に有効になり、後者で配列アクセスのundefinedを型に反映できます。
Q. import type と import の通常形はどう違いますか?
A. import typeは型情報だけを取り込み、コンパイル後のJSから完全に削除されます。バンドルサイズに影響しません。値も含むモジュールは通常のimportかimport { value, type T }のinline構文を使い分けます。
Q. 学習を加速したいです。スクールでまとめて学ぶ価値はありますか?
A. 独学で詰まる典型は「型エラーの読み方」と「any脱却の判断軸」です。レビュー文化のあるスクール(テックアカデミー・侍エンジニア・DMM WEBCAMP・レバテックカレッジ等)で、現役エンジニアのコードレビューを受けると、自分では気づかないany濫用や型注釈の冗長さを早期に直せます。短期間で実務水準に到達したい方には費用対効果が高い選択肢です。
まとめ
本記事ではTypeScriptの型の基礎を、tsconfig・プリミティブ・配列・タプル・オブジェクト・ユニオン・リテラル・narrowing・as const・satisfies・import typeまで、40本以上のコード例で網羅した。覚えるべきは「境界に型を書き、内部は推論に任せる」というシンプルな原則だ。strict: trueとnoUncheckedIndexedAccess: trueをONにし、外部入力はunknownで受けて型ガードで絞り込む——これだけで、any地獄からは確実に脱出できる。
次のステップは「ジェネリクスとユーティリティ型」「mapped type / conditional type」「Zod等のスキーマバリデーション」だ。型を書くスピードを上げ、レビューでのフィードバックを最短で受け取りたい場合、現役エンジニアによる伴走レビューがあるスクール(テックアカデミー・侍エンジニア・DMM WEBCAMP・レバテックカレッジ)を活用するのも、実務到達までの近道になる。

コメント