型の定義 (Type Definition)
TypeScriptの基本的な型定義方法をまとめます。
プリミティブ型
JavaScriptの基本的なデータ型に対応する型です。
string
: 文字列型'hello'
number
: 数値型123
,3.14
boolean
: 真偽値型true
,false
null
: null型null
undefined
: undefined型undefined
symbol
: シンボル型 (ES2015以降)Symbol('id')
bigint
: 大きな整数型 (ES2020以降)100n
let message: string = "こんにちは";
let count: number = 10;
let isDone: boolean = false;
let n: null = null;
let u: undefined = undefined;
let id: symbol = Symbol("unique");
let bigNum: bigint = 12345678901234567890n;
配列 (Array)
同じ型の要素の集合を表します。2通りの書き方があります。
型[]
Array<型>
// 数値の配列
let list1: number[] = [1, 2, 3];
let list2: Array<number> = [4, 5, 6];
// 文字列の配列
let names: string[] = ["Alice", "Bob"];
タプル (Tuple)
固定長の配列で、各要素の型が異なる場合に使用します。
let x: [string, number];
x = ["hello", 10]; // OK
// x = [10, "hello"]; // Error
// オプショナル要素 (? を使用)
let y: [string, number, boolean?];
y = ["goodbye", 20]; // OK
y = ["goodbye", 20, true]; // OK
オブジェクト (Object)
インラインで型を定義したり、インターフェースや型エイリアスを使用します。
// インライン定義
let user: { name: string; age: number; isActive?: boolean };
user = { name: "Sato", age: 30 }; // OK
user = { name: "Tanaka", age: 25, isActive: true }; // OK
// インターフェースを使用 (後述)
interface Point {
x: number;
y: number;
}
let p: Point = { x: 10, y: 20 };
// 型エイリアスを使用 (後述)
type UserID = string | number;
let userId: UserID = "user-123";
特殊な型
any
: どんな型でも許容します。型チェックを無効化するため、極力避けるべきです。unknown
:any
と同様にどんな型でも許容しますが、利用前に型チェックや型アサーションが必要です。any
より安全です。void
: 値を返さない関数の戻り値の型として主に使われます。undefined
を代入可能です。never
: 決して発生しない値の型。例外を投げる関数や、無限ループする関数の戻り値として使われます。
let anything: any = 4;
anything = "hello"; // OK
anything = false; // OK
let maybe: unknown = 10;
// let num: number = maybe; // Error: 'unknown' 型は 'number' 型に割り当てられません。
if (typeof maybe === 'number') {
let num: number = maybe; // OK (型ガード後)
}
function warnUser(): void {
console.log("This is a warning message");
}
function error(message: string): never {
throw new Error(message);
}
function infiniteLoop(): never {
while (true) {}
}
型エイリアス (Type Alias)
type
キーワードを使って、既存の型や複雑な型に別名を付けることができます。
type Point = {
x: number;
y: number;
};
type ID = string | number;
function printCoord(pt: Point) {
console.log("The coordinate's x value is " + pt.x);
console.log("The coordinate's y value is " + pt.y);
}
printCoord({ x: 100, y: 200 });
let userId: ID = 12345;
関数の型定義 (Function Type Definition)
関数の引数や戻り値に型を定義する方法です。
基本的な関数の型
// 名前付き関数
function add(x: number, y: number): number {
return x + y;
}
// 関数式 (匿名関数)
let multiply = function(x: number, y: number): number {
return x * y;
};
// アロー関数
let subtract = (x: number, y: number): number => {
return x - y;
};
引数の型
- 必須パラメータ: デフォルトで全てのパラメータは必須です。
- オプショナルパラメータ (
?
): パラメータ名の後に?
を付けると、その引数は省略可能になります。省略された場合、値はundefined
になります。オプショナルパラメータは必須パラメータの後ろに配置する必要があります。 - デフォルトパラメータ: パラメータにデフォルト値を指定できます。デフォルト値があるパラメータは自動的にオプショナルになります。
- レストパラメータ (
...
): 可変長の引数を配列として受け取ることができます。レストパラメータは必ず最後のパラメータである必要があります。
// オプショナルパラメータ
function buildNameOptional(firstName: string, lastName?: string): string {
if (lastName) {
return firstName + " " + lastName;
} else {
return firstName;
}
}
console.log(buildNameOptional("Bob")); // "Bob"
console.log(buildNameOptional("Bob", "Adams")); // "Bob Adams"
// デフォルトパラメータ
function buildNameDefault(firstName: string, lastName: string = "Smith"): string {
return firstName + " " + lastName;
}
console.log(buildNameDefault("Bob")); // "Bob Smith"
console.log(buildNameDefault("Bob", "Adams")); // "Bob Adams"
// レストパラメータ
function buildNameRest(firstName: string, ...restOfName: string[]): string {
return firstName + " " + restOfName.join(" ");
}
console.log(buildNameRest("Joseph", "Samuel", "Lucas", "MacKinzie")); // "Joseph Samuel Lucas MacKinzie"
関数型 (Function Type Expressions)
関数の型そのものを定義する方法です。
let myAdd: (baseValue: number, increment: number) => number;
myAdd = function(x: number, y: number): number {
return x + y;
};
// myAdd = function(x: string, y: string): string { return x + y; }; // Error
thisの型付け
関数の最初の引数としてthis
を記述することで、関数内でのthis
の型を指定できます。(このthis
パラメータはコンパイル後のJavaScriptコードには残りません)
interface Card {
suit: string;
card: number;
}
interface Deck {
suits: string[];
cards: number[];
createCardPicker(this: Deck): () => Card;
}
let deck: Deck = {
suits: ["hearts", "spades", "clubs", "diamonds"],
cards: Array(52),
// NOTE: The function now explicitly specifies that its callee must be of type Deck
createCardPicker: function(this: Deck) {
return () => {
let pickedCard = Math.floor(Math.random() * 52);
let pickedSuit = Math.floor(pickedCard / 13);
return { suit: this.suits[pickedSuit], card: pickedCard % 13 };
};
}
};
let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();
console.log("card: " + pickedCard.card + " of " + pickedCard.suit);
クラス (Classes)
オブジェクト指向プログラミングのクラス構文に関する型定義です。
基本的なクラス定義
class Greeter {
greeting: string; // プロパティ
constructor(message: string) { // コンストラクタ
this.greeting = message;
}
greet(): string { // メソッド
return "Hello, " + this.greeting;
}
}
let greeter = new Greeter("world");
console.log(greeter.greet()); // "Hello, world"
アクセス修飾子
public
: (デフォルト) どこからでもアクセス可能。private
: そのクラスの内部からのみアクセス可能。protected
: そのクラスおよび派生クラスの内部からアクセス可能。
class Animal {
private name: string; // このクラス内からのみアクセス可能
protected age: number; // このクラスとサブクラスからアクセス可能
public constructor(theName: string, theAge: number) {
this.name = theName;
this.age = theAge;
}
public move(distanceInMeters: number = 0) { // どこからでもアクセス可能
console.log(`${this.name} moved ${distanceInMeters}m.`);
}
}
class Dog extends Animal {
constructor(name: string, age: number) {
super(name, age);
}
public bark() {
console.log("Woof! Woof!");
console.log(`My age is ${this.age}`); // protected な age にアクセス可能
// console.log(`My name is ${this.name}`); // Error: name は private
}
}
const dog = new Dog("Max", 5);
dog.move(10); // public な move はアクセス可能
dog.bark();
// console.log(dog.age); // Error: age は protected
// console.log(dog.name); // Error: name は private
readonly修飾子
プロパティを読み取り専用にします。宣言時またはコンストラクタ内でのみ代入可能です。
class Octopus {
readonly name: string;
readonly numberOfLegs: number = 8;
constructor(theName: string) {
this.name = theName; // コンストラクタ内での代入はOK
}
setName(newName: string) {
// this.name = newName; // Error! name は読み取り専用
}
}
let dad = new Octopus("Man with the 8 strong legs");
// dad.name = "Man with the 3-piece suit"; // Error! name は読み取り専用
パラメータプロパティ
コンストラクタの引数にアクセス修飾子(public
, private
, protected
, readonly
)を付けることで、同名のプロパティ宣言とコンストラクタ内での代入を省略できます。
class Person {
// name: string; // 宣言不要
// constructor(name: string) { this.name = name; } // 代入不要
constructor(public readonly name: string, private age: number) {} // public readonly name と private age が自動的に定義される
introduce() {
console.log(`Hi, I'm ${this.name}, ${this.age} years old.`);
// this.name = "Tanaka"; // Error: readonly
}
}
const person = new Person("Suzuki", 40);
person.introduce(); // "Hi, I'm Suzuki, 40 years old."
console.log(person.name); // "Suzuki" (public)
// console.log(person.age); // Error: private
静的メンバ (static)
クラス自体に属するプロパティやメソッドです。インスタンス化せずにアクセスできます。
class Grid {
static origin = { x: 0, y: 0 }; // 静的プロパティ
calculateDistanceFromOrigin(point: { x: number; y: number }): number {
let xDist = point.x - Grid.origin.x;
let yDist = point.y - Grid.origin.y;
return Math.sqrt(xDist * xDist + yDist * yDist) / this.scale;
}
constructor(public scale: number) {}
}
console.log(Grid.origin); // { x: 0, y: 0 }
let grid1 = new Grid(1.0);
let grid5 = new Grid(5.0);
console.log(grid1.calculateDistanceFromOrigin({ x: 10, y: 10 })); // 14.14...
console.log(grid5.calculateDistanceFromOrigin({ x: 10, y: 10 })); // 2.82...
抽象クラス (abstract)
インスタンス化できないクラス。派生クラスで実装されるべき抽象メソッドを定義できます。
abstract class Department {
constructor(public name: string) {}
printName(): void {
console.log("Department name: " + this.name);
}
abstract printMeeting(): void; // 派生クラスでの実装が必須
}
class AccountingDepartment extends Department {
constructor() {
super("Accounting and Auditing"); // 派生クラスのコンストラクタは super() を呼び出す必要あり
}
printMeeting(): void { // 抽象メソッドの実装
console.log("The Accounting Department meets each Monday at 10am.");
}
generateReports(): void {
console.log("Generating accounting reports...");
}
}
let department: Department; // 抽象クラス型の変数を作成OK
// department = new Department(); // Error: 抽象クラスはインスタンス化できない
department = new AccountingDepartment(); // 具象サブクラスのインスタンスを作成・代入OK
department.printName();
department.printMeeting();
// department.generateReports(); // Error: Department 型に generateReports は存在しない
Getters / Setters
プロパティへのアクセスを制御するための特別なメソッドです。
class Employee {
private _fullName: string = "";
get fullName(): string {
console.log("Getter called");
return this._fullName;
}
set fullName(newName: string) {
console.log("Setter called");
if (newName && newName.length > 0) {
this._fullName = newName;
} else {
console.error("Name cannot be empty");
}
}
}
let employee = new Employee();
employee.fullName = "Bob Smith"; // Setter が呼び出される
console.log(employee.fullName); // Getter が呼び出される -> "Bob Smith"
employee.fullName = ""; // Setter が呼び出され、エラーメッセージが表示される
インターフェース (Interfaces)
オブジェクトの形状(構造)を定義するための強力な方法です。クラスが特定の規約に従うことを保証するためにも使用されます。
基本的なインターフェース
オブジェクトが持つべきプロパティとその型を定義します。
interface LabeledValue {
label: string;
size?: number; // オプショナルプロパティ
readonly value: number; // 読み取り専用プロパティ
}
function printLabel(labeledObj: LabeledValue) {
console.log(labeledObj.label);
if (labeledObj.size) {
console.log("Size:", labeledObj.size);
}
console.log("Value:", labeledObj.value);
// labeledObj.value = 10; // Error: readonly
}
let myObj: LabeledValue = { label: "Size 10 Object", value: 10 };
printLabel(myObj);
let myObjWithSize: LabeledValue = { label: "Size 20 Object", size: 20, value: 5 };
printLabel(myObjWithSize);
関数型インターフェース
関数のシグネチャ(引数の型と戻り値の型)を定義します。
interface SearchFunc {
(source: string, subString: string): boolean;
}
let mySearch: SearchFunc;
mySearch = function(src, sub) { // パラメータ名は一致しなくても良い
let result = src.search(sub);
return result > -1;
};
console.log(mySearch("hello world", "world")); // true
インデックス可能な型 (Indexable Types)
インデックス(数値または文字列)でアクセス可能な型を定義します。
interface StringArray {
[index: number]: string; // 数値インデックスシグネチャ
}
let myArray: StringArray;
myArray = ["Bob", "Fred"];
let myStr: string = myArray[0];
console.log(myStr); // "Bob"
interface NumberDictionary {
[index: string]: number; // 文字列インデックスシグネチャ
length: number; // OK: length は number なので文字列インデックスの型と一致
// name: string; // Error: name は string なので number に代入できない
}
let dict: NumberDictionary = {
"one": 1,
"two": 2,
length: 2
};
console.log(dict["one"]); // 1
console.log(dict.length); // 2
クラス型インターフェース (Class Types)
クラスが特定のインターフェースを実装(implements
)することを強制します。インターフェースはインスタンス側のメンバ(プロパティとメソッド)のみを記述し、静的メンバは記述しません。
interface ClockInterface {
currentTime: Date;
setTime(d: Date): void;
}
class Clock implements ClockInterface {
currentTime: Date = new Date();
setTime(d: Date) {
this.currentTime = d;
}
constructor(h: number, m: number) {} // 実装クラスは独自のメンバも持てる
}
let clock = new Clock(10, 30);
clock.setTime(new Date());
console.log(clock.currentTime);
コンストラクタシグネチャを持つインターフェースも定義できますが、これはクラス自体(静的側)の型を扱う場合に用います。
interface ClockConstructor {
new (hour: number, minute: number): ClockInterfaceInstance; // コンストラクタシグネチャ
}
interface ClockInterfaceInstance { // インスタンス側のインターフェース
tick(): void;
}
function createClock(ctor: ClockConstructor, hour: number, minute: number): ClockInterfaceInstance {
return new ctor(hour, minute);
}
class DigitalClock implements ClockInterfaceInstance {
constructor(h: number, m: number) {}
tick() {
console.log("beep beep");
}
}
class AnalogClock implements ClockInterfaceInstance {
constructor(h: number, m: number) {}
tick() {
console.log("tick tock");
}
}
let digital = createClock(DigitalClock, 12, 17);
let analog = createClock(AnalogClock, 7, 32);
digital.tick(); // "beep beep"
analog.tick(); // "tick tock"
インターフェースの継承 (Extending Interfaces)
インターフェースは他のインターフェースをextends
キーワードで継承し、メンバを組み合わせることができます。
interface Shape {
color: string;
}
interface PenStroke {
penWidth: number;
}
interface Square extends Shape, PenStroke { // 複数継承可能
sideLength: number;
}
let square = {} as Square; // 型アサーション(後述)で空オブジェクトをSquare型とする
square.color = "blue";
square.sideLength = 10;
square.penWidth = 5.0;
console.log(square); // { color: 'blue', sideLength: 10, penWidth: 5 }
ハイブリッド型 (Hybrid Types)
インターフェースは、関数型でありながら追加のプロパティも持つような、特殊な型を記述することもできます。
interface Counter {
(start: number): string; // 関数シグネチャ
interval: number; // プロパティ
reset(): void; // メソッド
}
function getCounter(): Counter {
let counter = ((start: number) => { /* ... */ }) as Counter; // 型アサーション
counter.interval = 123;
counter.reset = () => { /* ... */ };
return counter;
}
let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;
ジェネリクス (Generics)
型をパラメータ化することで、再利用可能で柔軟なコンポーネント(関数、クラス、インターフェース)を作成する機能です。
ジェネリック関数 (Generic Functions)
関数定義時に型パラメータ<T>
を宣言し、引数や戻り値の型として使用します。
// identity関数: 受け取った値をそのまま返す
function identity<T>(arg: T): T {
return arg;
}
// 呼び出し方1: 型引数を明示的に指定
let output1 = identity<string>("myString");
console.log(output1); // "myString" (型は string)
// 呼び出し方2: 型引数の推論を利用 (推奨)
let output2 = identity(123);
console.log(output2); // 123 (型は number)
型パラメータは複数定義できます。
function pair<T, U>(first: T, second: U): [T, U] {
return [first, second];
}
let result = pair<string, number>("age", 30);
console.log(result); // ["age", 30]
ジェネリックインターフェース (Generic Interfaces)
インターフェース定義時に型パラメータを使用します。
interface GenericIdentityFn<T> {
(arg: T): T;
}
function identity<T>(arg: T): T {
return arg;
}
let myIdentity1: GenericIdentityFn<number> = identity;
console.log(myIdentity1(100)); // 100
let myIdentity2: GenericIdentityFn<string> = identity;
console.log(myIdentity2("hello")); // "hello"
ジェネリッククラス (Generic Classes)
クラス定義時に型パラメータを使用します。
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
constructor(zero: T, addFn: (x: T, y: T) => T) {
this.zeroValue = zero;
this.add = addFn;
}
}
// 数値型の場合
let myNumeric = new GenericNumber<number>(0, (x, y) => x + y);
console.log(myNumeric.add(5, 10)); // 15
// 文字列型の場合
let myString = new GenericNumber<string>("", (x, y) => x + y);
console.log(myString.add("Hello, ", "world!")); // "Hello, world!"
ジェネリック制約 (Generic Constraints)
型パラメータT
が特定のプロパティやメソッドを持つことを保証したい場合、extends
キーワードを使って制約を追加します。
interface Lengthwise {
length: number;
}
// T は Lengthwise インターフェースを実装する型に制約される
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length); // .length プロパティが存在することが保証される
return arg;
}
loggingIdentity({ length: 10, value: 3 }); // OK
loggingIdentity("hello"); // OK (string は length を持つ)
loggingIdentity([1, 2, 3]); // OK (Array は length を持つ)
// loggingIdentity(3); // Error: number は length プロパティを持たない
// loggingIdentity({ value: 3 }); // Error: オブジェクトが length プロパティを持たない
ジェネリック制約における型パラメータの使用
ある型パラメータが、別の型パラメータのキーであることを制約できます。
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
let obj = { a: 1, b: "hello", c: true };
let valA = getProperty(obj, "a"); // number
let valB = getProperty(obj, "b"); // string
// let valD = getProperty(obj, "d"); // Error: Argument of type '"d"' is not assignable to parameter of type '"a" | "b" | "c"'.
デフォルト型引数 (Default Type Arguments)
型パラメータにデフォルトの型を指定できます。
interface Container<T = string> { // デフォルトは string
value: T;
}
let c1: Container<number> = { value: 123 }; // number 型で指定
let c2: Container = { value: "default" }; // デフォルトの string 型が使われる
型ユーティリティ (Type Utilities / Manipulation) 🔧
既存の型を変換したり、組み合わせたりして新しい型を作成するための便利な機能群です。
主要な型演算子
keyof T
: オブジェクト型T
のプロパティ名の集合を、文字列リテラルまたは数値リテラルの合併型 (Union Type) として取得します。typeof value
: 変数やプロパティvalue
の型を取得します。JavaScriptのtypeof
とは異なり、型システム内で使用されます。T[K]
(Indexed Access Types): 型T
のプロパティK
の型を取得します。K
はT
のキーの型(通常は文字列リテラル型やその合併型)である必要があります。T extends U ? X : Y
(Conditional Types): 型T
が型U
に代入可能であれば型X
に、そうでなければ型Y
になります。三項演算子のような条件分岐を型レベルで行います。[P in K]: T
(Mapped Types): 既存の型K
(通常はkeyof
で得られるキーの合併型)の各要素P
をキーとし、型T
を値とする新しいオブジェクト型を生成します。プロパティの修飾子(readonly
や?
)を変更することも可能です。- Template Literal Types: バッククォート(“) を使って文字列リテラル型を組み立てます。
// keyof
interface Person { name: string; age: number; }
type PersonKeys = keyof Person; // "name" | "age"
// typeof
let s = "hello";
let n: typeof s; // string
const personObj = { name: "Alice", age: 30 };
type PersonType = typeof personObj; // { name: string; age: number; }
// T[K]
type AgeType = Person["age"]; // number
type PersonPropType = Person[PersonKeys]; // string | number (Person["name"] | Person["age"])
// Conditional Types
type IsString<T> = T extends string ? "yes" : "no";
type Result1 = IsString<string>; // "yes"
type Result2 = IsString<number>; // "no"
// Mapped Types
type ReadonlyPerson = {
readonly [P in keyof Person]: Person[P]; // 全てのプロパティを readonly にする
};
// type ReadonlyPerson = { readonly name: string; readonly age: number; }
type OptionalPerson = {
[P in keyof Person]?: Person[P]; // 全てのプロパティをオプショナルにする
};
// type OptionalPerson = { name?: string | undefined; age?: number | undefined; }
// Template Literal Types
type World = "world";
type Greeting = `hello ${World}`; // "hello world"
type EmailLocaleIDs = "welcome_email" | "email_heading";
type FooterLocaleIDs = "footer_title" | "footer_sendoff";
type AllLocaleIDs = `${EmailLocaleIDs | FooterLocaleIDs}_id`; // "welcome_email_id" | "email_heading_id" | "footer_title_id" | "footer_sendoff_id"
type Lang = "en" | "ja" | "pt";
type LocaleMessageIDs = `${Lang}_${AllLocaleIDs}`; // "en_welcome_email_id" | "en_email_heading_id" | ... | "pt_footer_sendoff_id"
組み込みユーティリティ型 (Built-in Utility Types)
よく使われる型操作を簡単に行うためのユーティリティ型が多数用意されています。
ユーティリティ型 | 説明 | 例 |
---|---|---|
Partial<T> |
型T の全てのプロパティをオプショナル(? )にします。 |
|
Required<T> |
型T の全てのプロパティを必須にします (オプショナル? を取り除きます)。 |
|
Readonly<T> |
型T の全てのプロパティを読み取り専用(readonly )にします。 |
|
Record<K, T> |
キーの型がK (通常は文字列リテラル型の合併型やstring /number )、値の型がT であるオブジェクト型を作成します。 |
|
Pick<T, K> |
型T から、キーK (T のキーの合併型)で指定されたプロパティのみを抽出した新しい型を作成します。 |
|
Omit<T, K> |
型T から、キーK (文字列リテラル型の合併型)で指定されたプロパティを除外した新しい型を作成します。Pick の逆です。 |
|
Exclude<T, U> |
合併型T から、型U に代入可能な型を除外した新しい合併型を作成します。 |
|
Extract<T, U> |
合併型T から、型U に代入可能な型のみを抽出した新しい合併型を作成します。Exclude の逆です。 |
|
NonNullable<T> |
型T からnull とundefined を除外した型を作成します。 |
|
Parameters<T> |
関数型T の引数の型をタプル型として取得します。 |
|
ConstructorParameters<T> |
クラスのコンストラクタ型T の引数の型をタプル型として取得します。 |
|
ReturnType<T> |
関数型T の戻り値の型を取得します。 |
|
InstanceType<T> |
クラスのコンストラクタ型T のインスタンスの型を取得します。 |
|
Awaited<T> |
Promise などの “awaitable” な型T の中身の型(解決された値の型)を取得します。再帰的にPromise を解決します。 |
|
Uppercase<S> |
文字列リテラル型S を大文字に変換します。 |
|
Lowercase<S> |
文字列リテラル型S を小文字に変換します。 |
|
Capitalize<S> |
文字列リテラル型S の最初の文字を大文字に変換します。 |
|
Uncapitalize<S> |
文字列リテラル型S の最初の文字を小文字に変換します。 |
|
ThisParameterType<T> |
関数型 T の this パラメータの型を取得します。this パラメータがない場合は unknown になります。 |
|
OmitThisParameter<T> |
関数型 T から this パラメータを除いた新しい関数型を返します。 |
|
ThisType<T> |
マーカーとして機能し、オブジェクトリテラル内のコンテキスト上の this の型を指定します。実際には何も返さず、オブジェクトリテラルのコンテキストでのみ意味を持ちます。noImplicitThis オプションが必要です。 |
|
モジュール (Modules) 📦
コードをファイル単位で分割し、再利用性や保守性を高める仕組みです。TypeScriptは主にES Modules (ESM) 構文をサポートしますが、CommonJS (CJS) との相互運用性も考慮されています。
ES Modules (ESM)
ECMAScript標準のモジュールシステム。export
で公開し、import
で読み込みます。
// exporter.ts
export const pi = 3.14;
export function calculateCircumference(radius: number): number {
return 2 * pi * radius;
}
export default class Circle { // デフォルトエクスポート (1ファイルに1つまで)
constructor(public radius: number) {}
get area() {
return pi * this.radius * this.radius;
}
}
// importer.ts
import Circle, { pi, calculateCircumference as calcCirc } from './exporter'; // デフォルトと名前付きインポート
// import * as math from './exporter'; // 全てを math オブジェクトとしてインポート
console.log(pi); // 3.14
console.log(calcCirc(5)); // 31.4...
const myCircle = new Circle(10);
console.log(myCircle.area); // 314...
// console.log(math.pi); // 上記の * as math を使った場合
CommonJS (CJS) との相互運用
Node.jsで広く使われているモジュールシステム。module.exports
またはexports
で公開し、require()
で読み込みます。TypeScriptでは、ESM構文で記述しても、tsconfig.json
のmodule
設定に応じてCJS形式 (require
/module.exports
) にコンパイルできます。
- ESMからCJSを
import
する:esModuleInterop: true
(tsconfig.json
) が推奨されます。これにより、デフォルトエクスポートがないCJSモジュールもimport module from 'cjs-module'
のように自然にインポートできます。 - CJSからESMを
require()
する: 基本的に直接はできません。Dynamic Import (import()
) を使用する必要があります。 module: "NodeNext"
または"Node16"
: Node.jsの最新のモジュール解決ルール (package.jsonの"type": "module"
や.mjs
/.cjs
拡張子など) に従います。これにより、ESMとCJSの混在環境での動作がより正確になります。
// tsconfig.json (抜粋)
// {
// "compilerOptions": {
// "module": "NodeNext", // または "CommonJS" など
// "esModuleInterop": true // CJSインポートのために推奨
// }
// }
// --- ESMからCJSをインポート ---
// commonjs-module.js
// module.exports = { value: 42 };
import cjsModule from './commonjs-module'; // esModuleInterop: true の場合
// import * as cjsModuleStar from './commonjs-module'; // デフォルトエクスポートがない場合、こちらが基本
// console.log(cjsModule.value); // 42
// --- CJSからESMをインポート ---
// esm-module.ts
// export const value = 123;
// commonjs-importer.js (または .cts)
// const esmModule = require('./esm-module'); // Error: Cannot use import statement outside a module (コンパイル後JSでの実行時エラー)
async function loadEsm() {
const esmModule = await import('./esm-module'); // Dynamic Import を使用
console.log(esmModule.value); // 123
}
loadEsm();
動的インポート (Dynamic Imports)
import()
構文を使用すると、必要なタイミングでモジュールを非同期に読み込むことができます。Promiseを返します。
async function loadUtility() {
if (someCondition) {
const utils = await import('./utilities'); // 必要になったらロード
utils.doSomething();
}
}
名前空間 (Namespaces)
古いTypeScriptの機能で、コードをグループ化する方法です(以前はInternal Modulesと呼ばれていました)。現在はES Modulesの使用が推奨されますが、グローバル変数の汚染を防ぐためや、大規模なレガシーコードで見かけることがあります。
// validation.ts
namespace Validation {
export interface StringValidator {
isAcceptable(s: string): boolean;
}
const lettersRegexp = /^[A-Za-z]+$/;
export class LettersOnlyValidator implements StringValidator {
isAcceptable(s: string) {
return lettersRegexp.test(s);
}
}
}
// main.ts
/// <reference path="validation.ts" /> // 複数ファイルの場合は参照タグが必要な場合がある
let validator = new Validation.LettersOnlyValidator();
console.log(validator.isAcceptable("HelloWorld")); // true
Enum (列挙型) 🔢
関連する定数の集まりに名前を付ける方法です。
数値Enum (Numeric Enums)
デフォルトでは、最初のメンバーが0
で始まり、以降は1ずつインクリメントされます。明示的に値を設定することも可能です。
enum Direction {
Up, // 0
Down, // 1
Left, // 2
Right // 3
}
enum ResponseStatus {
No = 0,
Yes = 1,
}
enum FileAccess {
None, // 0
Read = 1 << 1, // 2 (ビットフラグ例)
Write = 1 << 2, // 4
ReadWrite = Read | Write, // 6
}
console.log(Direction.Up); // 0
console.log(Direction[0]); // "Up" (リバースマッピング)
console.log(FileAccess.ReadWrite); // 6
文字列Enum (String Enums)
各メンバーに文字列値を明示的に割り当てます。数値Enumのようなリバースマッピングは生成されません。
enum LogLevel {
Error = "ERROR",
Warn = "WARN",
Info = "INFO",
Debug = "DEBUG",
}
console.log(LogLevel.Error); // "ERROR"
// console.log(LogLevel["ERROR"]); // Error (リバースマッピングなし)
混合Enum (Heterogeneous Enums)
数値と文字列のメンバーを混在させることも可能ですが、一般的には推奨されません。
enum Mixed {
No = 0,
Yes = "YES",
}
const enum
コンパイル時にEnumへの参照が全てインライン展開され、Enumオブジェクト自体は生成されません。パフォーマンス上の利点がありますが、デバッグが少し難しくなる場合があります。
const enum Color { Red, Green, Blue }
let c: Color = Color.Green;
console.log(c); // コンパイル後のJS: console.log(1);
Ambient Enums (declare enum)
既存のEnumオブジェクト(例えば外部JavaScriptライブラリによって定義されたもの)の型を宣言するために使用します。
declare enum ExistingEnum {
A = 1,
B,
C = 2
}
let e: ExistingEnum = ExistingEnum.A;
注意: EnumはTypeScript独自の機能であり、標準JavaScriptには存在しません。特にconst enum
でない場合、実行時にオブジェクトが生成されます。Union Types + Literal Typesで代替できるケースも多いです (例: type Direction = "Up" | "Down" | "Left" | "Right";
)。
デコレータ (Decorators) @
デコレータはECMAScriptの提案段階にある実験的な機能です。将来的に構文や挙動が変更される可能性があります。使用するには tsconfig.json
で "experimentalDecorators": true
と "emitDecoratorMetadata": true
(メタデータを使う場合) を設定する必要があります。
クラス、メソッド、アクセサ、プロパティ、パラメータに付与できる特殊な宣言で、メタプログラミングに使用されます。
クラスデコレータ
クラスのコンストラクタに適用され、クラス定義の監視、変更、置換に使用できます。
// シンプルなクラスデコレータ
function sealed(constructor: Function) {
Object.seal(constructor);
Object.seal(constructor.prototype);
console.log(`Class ${constructor.name} is sealed.`);
}
@sealed
class BugReport {
type = "report";
title: string;
constructor(t: string) {
this.title = t;
}
}
const report = new BugReport("My bug");
// BugReport.prototype.newMethod = () => {}; // Error: Cannot add property newMethod, object is not extensible
// デコレータファクトリ (引数を取るデコレータ)
function Component(options: { id: string }) {
return function<T extends { new (...args: any[]): {} }>(constructor: T) {
console.log(`Component ${options.id} applied to ${constructor.name}`);
return class extends constructor { // クラスを拡張して返すことも可能
componentId = options.id;
};
}
}
@Component({ id: "my-component" })
class MyClass {
// ...
}
const instance = new MyClass() as any;
console.log(instance.componentId); // "my-component"
メソッドデコレータ
メソッド宣言に適用され、メソッドのディスクリプタ(value
, writable
, enumerable
, configurable
)を監視、変更、置換できます。
引数:
- クラスのコンストラクタ関数(静的メンバの場合)またはクラスのプロトタイプ(インスタンスメンバの場合)
- メンバの名前 (string | symbol)
- メンバのプロパティディスクリプタ (
PropertyDescriptor
)
function enumerable(value: boolean) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
descriptor.enumerable = value;
console.log(`Method ${propertyKey} enumerable set to ${value}`);
};
}
class Person {
constructor(private name: string) {}
@enumerable(false) // このメソッドは for...in ループなどで列挙されなくなる
greet() {
return "Hello, " + this.name;
}
}
const p = new Person("Alice");
for (const key in p) {
console.log(key); // greet は表示されない
}
アクセサデコレータ
アクセサ(getter/setter)宣言に適用されます。引数や機能はメソッドデコレータと同様ですが、get
またはset
の片方にのみ適用できます(両方にはできません)。
function configurable(value: boolean) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
descriptor.configurable = value;
console.log(`Accessor ${propertyKey} configurable set to ${value}`);
};
}
class Point {
private _x: number = 0;
private _y: number = 0;
@configurable(false) // このアクセサは delete や defineProperty で再定義できなくなる
get x() { return this._x; }
set x(val: number) { this._x = val; } // getter に付ければ setter も対象になる
}
const pt = new Point();
// delete pt.x; // Error in strict mode
プロパティデコレータ
プロパティ宣言に適用されます。プロパティディスクリプタは引数として渡されません。
引数:
- クラスのコンストラクタ関数(静的メンバの場合)またはクラスのプロトタイプ(インスタンスメンバの場合)
- メンバの名前 (string | symbol)
import "reflect-metadata"; // reflect-metadataライブラリが必要な場合
function format(formatString: string) {
return function (target: any, propertyKey: string) {
Reflect.defineMetadata("format", formatString, target, propertyKey);
console.log(`Format metadata set for ${propertyKey}`);
};
}
function getFormat(target: any, propertyKey: string): string | undefined {
return Reflect.getMetadata("format", target, propertyKey);
}
class User {
@format("Hello, %s")
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
let formatString = getFormat(this, "greeting");
return formatString ? formatString.replace("%s", this.greeting) : this.greeting;
}
}
const user = new User("World");
console.log(user.greet()); // "Hello, World"
パラメータデコレータ
コンストラクタまたはメソッドのパラメータ宣言に適用されます。
引数:
- クラスのコンストラクタ関数(静的メンバの場合)またはクラスのプロトタイプ(インスタンスメンバの場合)
- メンバの名前 (string | symbol | undefined) – コンストラクタの場合は undefined
- パラメータリスト内でのパラメータのインデックス (数値)
import "reflect-metadata";
function required(target: Object, propertyKey: string | symbol, parameterIndex: number) {
let existingRequiredParameters: number[] = Reflect.getOwnMetadata("required", target, propertyKey) || [];
existingRequiredParameters.push(parameterIndex);
Reflect.defineMetadata("required", existingRequiredParameters, target, propertyKey);
console.log(`Parameter at index ${parameterIndex} of ${String(propertyKey)} marked as required.`);
}
function validate(target: any, propertyName: string, descriptor: TypedPropertyDescriptor<Function>) {
let method = descriptor.value!;
descriptor.value = function (...args: any[]) {
let requiredParameters: number[] = Reflect.getOwnMetadata("required", target, propertyName);
if (requiredParameters) {
for (let parameterIndex of requiredParameters) {
if (parameterIndex >= args.length || args[parameterIndex] === undefined || args[parameterIndex] === null) {
throw new Error(`Missing required argument at index ${parameterIndex} for method ${propertyName}.`);
}
}
}
return method.apply(this, args);
};
}
class Calculator {
@validate
add(@required x: number | undefined, @required y: number) {
return x! + y;
}
}
const calc = new Calculator();
console.log(calc.add(5, 3)); // 8
// calc.add(5, undefined); // Error: Missing required argument at index 1 for method add.
// calc.add(undefined, 3); // Error: Missing required argument at index 0 for method add.
型アサーションと型ガード (Type Assertions & Type Guards)🛡️
コンパイラが型を特定できない場合に、開発者が型情報を伝える仕組みです。
型アサーション (Type Assertions)
コンパイラに対して「この値は特定の型である」と伝える方法です。型チェックを行わず、コンパイル時のエラーを抑制するだけなので、実行時エラーの原因になり得ます。開発者が型についてコンパイラより詳しい場合にのみ使用すべきです。
2つの構文があります:
as
構文:value as Type
(JSXを使う.tsx
ファイルではこちらが推奨されます)- 山括弧 (angle-bracket) 構文:
<Type>value
let someValue: unknown = "this is a string";
// as 構文
let strLength1: number = (someValue as string).length;
// 山括弧構文
let strLength2: number = (<string>someValue).length;
console.log(strLength1, strLength2); // 16 16
// 間違ったアサーションは実行時エラーを引き起こす
let गलतValue: unknown = 123;
// let len: number = (गलतValue as string).length; // コンパイルは通るが実行時にエラー (TypeError: गलतValue.length is not a function)
非nullアサーション演算子 (!)
値がnull
でもundefined
でもないとコンパイラに伝える後置演算子です。型アサーションと同様に、コンパイル時のチェックをバイパスするだけです。
function process(value: string | null | undefined) {
// const len = value.length; // Error: Object is possibly 'null' or 'undefined'.
const lenAssured = value!.length; // OK (開発者が null/undefined でないことを保証)
console.log(lenAssured);
}
process("hello"); // 5
// process(null); // コンパイルは通るが実行時にエラー (TypeError: Cannot read properties of null (reading 'length'))
型ガード (Type Guards)
特定のスコープ内で変数の型をより具体的な型に絞り込むための式です。if
文などの条件分岐と組み合わせて使われます。
typeof
: プリミティブ型 ("string"
,"number"
,"bigint"
,"boolean"
,"symbol"
,"undefined"
,"object"
,"function"
) のチェックに使用します。instanceof
: 値が特定のクラスのインスタンスであるか、またはそのプロトタイプチェーン上に特定のコンストラクタを持つかチェックします。in
演算子: オブジェクトが特定のプロパティを持っているかチェックします。- ユーザー定義型ガード (User-Defined Type Guards):
parameterName is Type
という形式の戻り値型を持つ関数を作成します。この関数がtrue
を返した場合、TypeScriptはそのスコープ内でparameterName
をType
として扱います。
function processValue(value: string | number | Date | Fish | Bird) {
// typeof 型ガード
if (typeof value === "string") {
console.log("String length:", value.toUpperCase()); // value は string 型
} else if (typeof value === "number") {
console.log("Number fixed:", value.toFixed(2)); // value は number 型
}
// instanceof 型ガード
else if (value instanceof Date) {
console.log("Date year:", value.getFullYear()); // value は Date 型
}
// ユーザー定義型ガード (isFish)
else if (isFish(value)) {
value.swim(); // value は Fish 型
}
// in 型ガード (Bird の判定)
else if ("fly" in value) {
value.fly(); // value は Bird 型 (構造的部分型付けによる推論)
}
}
// ユーザー定義型ガードの例
interface Fish { swim(): void; }
interface Bird { fly(): void; }
function isFish(pet: Fish | Bird): pet is Fish { // 戻り値の型が 'pet is Fish'
return (pet as Fish).swim !== undefined;
}
let myFish: Fish = { swim: () => console.log("Swimming...") };
let myBird: Bird = { fly: () => console.log("Flying...") };
processValue("hello");
processValue(123.456);
processValue(new Date());
processValue(myFish);
processValue(myBird);
高度な型 (Advanced Types) ✨
より複雑な型の関係性を表現するための機能です。
合併型 (Union Types |)
複数の型のうちのいずれか一つであることを示します。
function padLeft(value: string, padding: string | number) {
if (typeof padding === "number") {
return Array(padding + 1).join(" ") + value;
}
if (typeof padding === "string") {
return padding + value;
}
throw new Error(`Expected string or number, got '${typeof padding}'.`);
}
console.log(padLeft("Hello", 4)); // " Hello"
console.log(padLeft("Hello", "---")); // "---Hello"
// console.log(padLeft("Hello", true)); // Error (コンパイル時)
交差型 (Intersection Types &)
複数の型を結合し、すべての型のメンバを持つ新しい型を作成します。
interface Person {
name: string;
age: number;
}
interface Employee {
employeeId: string;
startDate: Date;
}
// Person と Employee の両方のプロパティを持つ型
type EmployeePerson = Person & Employee;
let employeePerson: EmployeePerson = {
name: "Alice",
age: 30,
employeeId: "E123",
startDate: new Date()
};
console.log(employeePerson.name); // Alice
console.log(employeePerson.employeeId); // E123
判別可能な合併型 (Discriminated Unions / Tagged Unions)
合併型の各要素が共通のリテラル型のプロパティ(判別子/タグ)を持つパターンです。switch
文やif
文で判別子をチェックすることで、型を安全に絞り込むことができます。
interface Square {
kind: "square"; // 判別子
size: number;
}
interface Rectangle {
kind: "rectangle"; // 判別子
width: number;
height: number;
}
interface Circle {
kind: "circle"; // 判別子
radius: number;
}
type Shape = Square | Rectangle | Circle; // 判別可能な合併型
function getArea(shape: Shape): number {
switch (shape.kind) { // 判別子で分岐
case "square":
return shape.size * shape.size; // shape は Square 型
case "rectangle":
return shape.width * shape.height; // shape は Rectangle 型
case "circle":
return Math.PI * shape.radius ** 2; // shape は Circle 型
default:
// never 型チェック: もし Shape に新しい型が追加された場合、
// ここでコンパイルエラーが発生し、対応漏れを防げる
const _exhaustiveCheck: never = shape;
return _exhaustiveCheck;
}
}
let mySquare: Square = { kind: "square", size: 5 };
let myCircle: Circle = { kind: "circle", radius: 3 };
console.log(getArea(mySquare)); // 25
console.log(getArea(myCircle)); // 28.27...
型述語 (Type Predicates is)
ユーザー定義型ガード関数で使われ、関数が `true` を返した場合に引数が特定の型であることをコンパイラに伝えます。
function isStringArray(arr: unknown): arr is string[] {
return Array.isArray(arr) && arr.every(item => typeof item === 'string');
}
let mixedArray: unknown = ["a", "b", 1, "c"];
let stringArray: unknown = ["x", "y", "z"];
if (isStringArray(mixedArray)) {
// mixedArray はここでは string[] 型として扱われない
console.log("Mixed array is all strings:", mixedArray.join(", "));
} else {
console.log("Mixed array is not all strings"); // こちらが実行される
}
if (isStringArray(stringArray)) {
// stringArray はここでは string[] 型として扱われる
console.log("String array is all strings:", stringArray.map(s => s.toUpperCase()).join(", ")); // こちらが実行される: X, Y, Z
} else {
console.log("String array is not all strings");
}
tsconfig.json の主要オプション ⚙️
TypeScriptコンパイラ (tsc
) の動作を設定するファイルです。プロジェクトのルートに配置します。よく使われる主要なオプションをまとめます。
npx tsc --init
コマンドで基本的な tsconfig.json
を生成できます。
カテゴリ | オプション | 説明 | 一般的な設定例 |
---|---|---|---|
コンパイル対象 | include |
コンパイル対象に含めるファイル/ディレクトリのパターンを指定します。(デフォルト: **/* ) |
["src/**/*"] |
exclude |
コンパイル対象から除外するファイル/ディレクトリのパターンを指定します。(デフォルト: node_modules , bower_components , jspm_packages , outDir ) |
["node_modules", "dist", "**/*.spec.ts"] |
|
files |
コンパイル対象のファイルを個別に指定します。include とは併用しないことが多いです。 |
["main.ts", "utils.ts"] |
|
型チェック (Strictness) | strict |
全ての厳格な型チェックオプション (noImplicitAny , strictNullChecks , strictFunctionTypes , strictBindCallApply , strictPropertyInitialization , noImplicitThis , useUnknownInCatchVariables , alwaysStrict ) を有効にします。強く推奨 (true )。 |
true |
noImplicitAny |
型が明示されず、推論もできない場合にany 型とみなすことを禁止します。(strict: true に含まれる) |
true (strict に含まれる) |
|
strictNullChecks |
null と undefined を全ての型の許容値から除外し、明示的に扱わなければならないようにします。(strict: true に含まれる) |
true (strict に含まれる) |
|
strictFunctionTypes |
関数のパラメータの型チェックを共変 (covariant) ではなく反変 (contravariant) にします。より厳密な関数型のチェック。(strict: true に含まれる) |
true (strict に含まれる) |
|
noUnusedLocals |
未使用のローカル変数を許可しません。 | true (任意) |
|
noUnusedParameters |
未使用の関数パラメータを許可しません。 | true (任意) |
|
モジュール関連 | module |
生成するJavaScriptコードのモジュールシステムを指定します。 | Node.js: "NodeNext" or "CommonJS" ブラウザ/バンドラ: "ESNext" , "ES2022" , "ES2020" |
moduleResolution |
モジュールの解決方法を指定します。module オプションや環境によって適切な値が異なります。 |
Node.js: "NodeNext" or "Node16" (module がNodeNext /Node16 の場合)バンドラ: "Bundler" |
|
esModuleInterop |
CommonJSモジュールとESモジュール間の相互運用性を向上させるためのヘルパーコードを生成します。true 推奨。 |
true |
|
resolveJsonModule |
.json ファイルのインポートを許可します。 |
true (必要なら) |
|
出力関連 | target |
生成するJavaScriptのECMAScriptバージョンを指定します。実行環境に合わせて選択します。 | "ES2022" , "ES2021" , "ES6" ("ES2015" ) など |
outDir |
コンパイル結果のJavaScriptファイルを出力するディレクトリを指定します。 | "./dist" |
|
rootDir |
コンパイル対象のソースファイルが含まれるルートディレクトリを指定します。outDir と併用し、出力先のディレクトリ構造を維持します。 |
"./src" |
|
declaration |
対応する.d.ts (型定義) ファイルを生成します。ライブラリ開発時にtrue にします。 |
true (ライブラリの場合) |
|
sourceMap |
.js.map ソースマップファイルを生成します。デバッグに役立ちます。 |
true |
|
removeComments |
出力されるJavaScriptファイルからコメントを削除します。 | true (本番ビルド時など) |
|
その他 | lib |
コンパイルに含める組み込みライブラリファイル (例: DOM API, ES2022の機能など) を指定します。target に応じてデフォルト値が決まりますが、明示的に指定することも可能です。 |
["ES2022", "DOM", "DOM.Iterable"] (ブラウザ向け) |
jsx |
JSXコードをどのように処理するかを指定します。Reactなどで使用します。 | "react" , "react-jsx" , "preserve" など |
|
allowJs |
JavaScriptファイル (.js ) をTypeScriptプロジェクトに含めてコンパイルすることを許可します。JSからTSへの移行時に便利です。 |
true (必要なら) |
|
skipLibCheck |
依存関係の型定義ファイル (.d.ts ) の型チェックをスキップします。コンパイル時間を短縮できます。true 推奨。 |
true |
|
forceConsistentCasingInFileNames |
ファイル名の大文字・小文字の区別を一貫させます。異なるOS間での問題を避けるために true 推奨。 |
true |
|
baseUrl & paths |
モジュールの絶対パス解決の基点 (baseUrl ) と、特定のパスへのエイリアス (paths ) を設定します。大規模プロジェクトでのインポートパスを簡潔にします。 |
"baseUrl": "./", "paths": { "@/*": ["src/*"] } |
コメント