このページは非推奨になりました

このハンドブックページは置き換えられました。新しいページに移動してください

関数

関数は、JavaScriptにおけるあらゆるアプリケーションの基本的な構成要素です。関数は、クラス、情報隠蔽、モジュールを模倣して、抽象化の層を構築する方法です。TypeScriptでは、クラス、名前空間、モジュールがありますが、関数は依然として、物事を行う方法を記述する上で重要な役割を果たします。また、TypeScriptは、標準のJavaScript関数にいくつかの新しい機能を追加して、より使いやすくしています。

関数

まず、JavaScriptと同様に、TypeScript関数は、名前付き関数としても無名関数としても作成できます。これにより、APIに関数のリストを構築する場合でも、別の関数に渡す一回限りの関数でも、アプリケーションに最適なアプローチを選択できます。

JavaScriptでこれら2つのアプローチがどのように見えるかを簡単にまとめます

ts
// Named function
function add(x, y) {
return x + y;
}
 
// Anonymous function
let myAdd = function (x, y) {
return x + y;
};
Try

JavaScriptと同様に、関数は関数本体の外にある変数を参照できます。その場合、これらの変数をキャプチャすると言われます。これがどのように機能するか(およびこの手法を使用する場合のトレードオフ)を理解することは、この記事の範囲外ですが、このメカニズムがどのように機能するかをしっかりと理解することは、JavaScriptとTypeScriptを使用する上で重要な要素です。

ts
let z = 100;
 
function addToZ(x, y) {
return x + y + z;
}
Try

関数型

関数に型を付ける

先ほどの簡単な例に型を追加してみましょう

ts
function add(x: number, y: number): number {
return x + y;
}
 
let myAdd = function (x: number, y: number): number {
return x + y;
};
Try

各パラメータに型を追加し、次に関数自体に追加して、戻り値の型を追加できます。TypeScriptは、returnステートメントを見ることで戻り値の型を判断できるため、多くの場合、オプションでこれを省略することもできます。

関数型を記述する

関数に型を付けたので、関数型の各部分を見て、関数の完全な型を記述しましょう。

ts
let myAdd: (x: number, y: number) => number = function (
x: number,
y: number
): number {
return x + y;
};
Try

関数の型には、引数の型と戻り値の型の2つの部分があります。関数全体の型を記述するときは、両方の部分が必要です。パラメータの型は、各パラメータに名前と型を付けて、パラメータリストのように記述します。この名前は、読みやすさを向上させるためのものです。代わりに、次のように記述することもできました

ts
let myAdd: (baseValue: number, increment: number) => number = function (
x: number,
y: number
): number {
return x + y;
};
Try

パラメータの型が一致する限り、関数型でパラメータに指定する名前に関係なく、それは関数の有効な型と見なされます。

2番目の部分は戻り値の型です。パラメーターと戻り値の型の間に矢印 (=>) を使用して、どちらが戻り値の型であるかを明確にします。前述のように、これは関数型に必須の部分であるため、関数が値を返さない場合は、省略する代わりに void を使用します。

特筆すべきは、関数型を構成するのはパラメーターと戻り値の型だけであるということです。キャプチャされた変数は型に反映されません。事実上、キャプチャされた変数は任意の関数の「隠れた状態」の一部であり、その API を構成するものではありません。

型の推論

例を試しているうちに、TypeScript コンパイラーが、式の片側にのみ型がある場合でも、型を把握できることに気付くかもしれません。

ts
// The parameters 'x' and 'y' have the type number
let myAdd = function (x: number, y: number): number {
return x + y;
};
 
// myAdd has the full function type
let myAdd2: (baseValue: number, increment: number) => number = function (x, y) {
return x + y;
};
Try

これは「文脈型付け」と呼ばれ、型推論の一種です。これは、プログラムを型付きにするための労力を削減するのに役立ちます。

オプションパラメーターとデフォルトパラメーター

TypeScript では、すべてのパラメーターは関数で必須であると想定されています。これは、null または undefined を指定できないという意味ではなく、むしろ、関数が呼び出されたときに、コンパイラーが、ユーザーが各パラメーターに値を指定したことを確認することを意味します。また、コンパイラーは、これらのパラメーターが関数に渡される唯一のパラメーターであると想定します。簡単に言えば、関数に渡される引数の数は、関数が予期するパラメーターの数と一致する必要があります。

ts
function buildName(firstName: string, lastName: string) {
return firstName + " " + lastName;
}
 
let result1 = buildName("Bob"); // error, too few parameters
Expected 2 arguments, but got 1.2554Expected 2 arguments, but got 1.
let result2 = buildName("Bob", "Adams", "Sr."); // error, too many parameters
Expected 2 arguments, but got 3.2554Expected 2 arguments, but got 3.
let result3 = buildName("Bob", "Adams"); // ah, just right
Try

JavaScript では、すべてのパラメーターはオプションであり、ユーザーは適宜省略できます。省略すると、値は undefined になります。オプションにするパラメーターの末尾に ? を追加することで、TypeScript でこの機能を利用できます。たとえば、上記の姓パラメーターをオプションにしたいとしましょう。

ts
function buildName(firstName: string, lastName?: string) {
if (lastName) return firstName + " " + lastName;
else return firstName;
}
 
let result1 = buildName("Bob"); // works correctly now
let result2 = buildName("Bob", "Adams", "Sr."); // error, too many parameters
Expected 1-2 arguments, but got 3.2554Expected 1-2 arguments, but got 3.
let result3 = buildName("Bob", "Adams"); // ah, just right
Try

オプションのパラメーターは、必須のパラメーターの後に続く必要があります。姓ではなく名をオプションにする場合は、関数内のパラメーターの順序を変更し、名がリストの最後になるようにする必要があります。

TypeScript では、ユーザーが値を指定しない場合、またはユーザーが undefined を代わりに渡した場合に、パラメーターに割り当てられる値を設定することもできます。これらはデフォルトで初期化されたパラメーターと呼ばれます。前の例を取り上げて、姓をデフォルトで "Smith" に設定しましょう。

ts
function buildName(firstName: string, lastName = "Smith") {
return firstName + " " + lastName;
}
 
let result1 = buildName("Bob"); // works correctly now, returns "Bob Smith"
let result2 = buildName("Bob", undefined); // still works, also returns "Bob Smith"
let result3 = buildName("Bob", "Adams", "Sr."); // error, too many parameters
Expected 1-2 arguments, but got 3.2554Expected 1-2 arguments, but got 3.
let result4 = buildName("Bob", "Adams"); // ah, just right
Try

すべての必須パラメーターの後に続くデフォルトで初期化されたパラメーターはオプションとして扱われ、オプションのパラメーターと同様に、それぞれの関数を呼び出すときに省略できます。つまり、オプションのパラメーターと末尾のデフォルトパラメーターは、型に共通性を持つことになり、両方とも

ts
function buildName(firstName: string, lastName?: string) {
// ...
}

そして

ts
function buildName(firstName: string, lastName = "Smith") {
// ...
}

同じ型 (firstName: string, lastName?: string) => string を共有します。lastName のデフォルト値は型では消え、パラメーターがオプションであるという事実だけが残ります。

単純なオプションのパラメーターとは異なり、デフォルトで初期化されたパラメーターは、必須パラメーターの後に続く必要はありません。デフォルトで初期化されたパラメーターが必須パラメーターの前にある場合、ユーザーはデフォルトで初期化された値を取得するために明示的に undefined を渡す必要があります。たとえば、firstName にのみデフォルトの初期化子を使用して、最後の例を記述できます。

ts
function buildName(firstName = "Will", lastName: string) {
return firstName + " " + lastName;
}
 
let result1 = buildName("Bob"); // error, too few parameters
Expected 2 arguments, but got 1.2554Expected 2 arguments, but got 1.
let result2 = buildName("Bob", "Adams", "Sr."); // error, too many parameters
Expected 2 arguments, but got 3.2554Expected 2 arguments, but got 3.
let result3 = buildName("Bob", "Adams"); // okay and returns "Bob Adams"
let result4 = buildName(undefined, "Adams"); // okay and returns "Will Adams"
Try

残余パラメーター

必須、オプション、およびデフォルトのパラメーターには、1 つの共通点があります。それは、一度に 1 つのパラメーターについて語るということです。場合によっては、複数のパラメーターをグループとして操作したり、関数が最終的に取得するパラメーターの数を把握していない場合があります。JavaScript では、すべての関数本体内で表示される arguments 変数を使用して、引数を直接操作できます。

TypeScript では、これらの引数を変数にまとめることができます。

ts
function buildName(firstName: string, ...restOfName: string[]) {
return firstName + " " + restOfName.join(" ");
}
 
// employeeName will be "Joseph Samuel Lucas MacKinzie"
let employeeName = buildName("Joseph", "Samuel", "Lucas", "MacKinzie");
Try

残余パラメーターは、無制限の数のオプションのパラメーターとして扱われます。残余パラメーターに引数を渡す場合、必要な数だけ使用できます。1 つも渡さないこともできます。コンパイラーは、省略記号 (...) の後に指定された名前で渡された引数の配列を作成し、関数で使用できるようにします。

省略記号は、残余パラメーターを使用した関数の型でも使用されます。

ts
function buildName(firstName: string, ...restOfName: string[]) {
return firstName + " " + restOfName.join(" ");
}
 
let buildNameFun: (fname: string, ...rest: string[]) => string = buildName;
Try

this

JavaScript で this を使用する方法を学ぶことは、通過儀礼のようなものです。TypeScript は JavaScript のスーパーセットであるため、TypeScript 開発者も this の使用方法と、正しく使用されていない場合の見分け方を学ぶ必要があります。幸いなことに、TypeScript では、いくつかのテクニックを使用して this の誤った使用をキャッチできます。ただし、JavaScript で this がどのように機能するかを学ぶ必要がある場合は、まず Yehuda Katz の JavaScript の関数呼び出しと「this」の理解をお読みください。Yehuda の記事では this の内部動作について非常にうまく説明されているため、ここでは基本事項のみを説明します。

this とアロー関数

JavaScript では、this は関数が呼び出されたときに設定される変数です。これにより、非常に強力で柔軟な機能になりますが、関数が実行されているコンテキストについて常に知っておく必要があるというコストが発生します。これは、特に関数を返したり、関数を引数として渡したりする場合に、非常に混乱を招く可能性があります。

例を見てみましょう

ts
let deck = {
suits: ["hearts", "spades", "clubs", "diamonds"],
cards: Array(52),
createCardPicker: function () {
return function () {
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();
 
alert("card: " + pickedCard.card + " of " + pickedCard.suit);
Try

createCardPicker は、関数自体が関数を返す関数であることに注意してください。例を実行しようとすると、予期されたアラート ボックスの代わりにエラーが発生します。これは、createCardPicker によって作成された関数で使用されている this が、deck オブジェクトではなく window に設定されるためです。これは、cardPicker() を単独で呼び出すためです。このようなトップレベルの非メソッド構文呼び出しでは、thiswindow が使用されます。(注:厳密モードでは、thiswindow ではなく undefined になります。)

後で使用するために関数を返す前に、関数が正しい this にバインドされていることを確認することで、これを修正できます。こうすることで、後でどのように使用されても、元の deck オブジェクトを参照できるようになります。これを行うには、関数式を ECMAScript 6 アロー構文を使用するように変更します。アロー関数は、関数が呼び出された場所ではなく、作成された場所で this をキャプチャします。

ts
let deck = {
suits: ["hearts", "spades", "clubs", "diamonds"],
cards: Array(52),
createCardPicker: function () {
// NOTE: the line below is now an arrow function, allowing us to capture 'this' right here
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();
 
alert("card: " + pickedCard.card + " of " + pickedCard.suit);
Try

さらに、noImplicitThis フラグをコンパイラーに渡すと、TypeScript はこの間違いを犯したときに警告します。this.suits[pickedSuit]thisany 型であることを指摘します。

this パラメーター

残念ながら、this.suits[pickedSuit] の型は依然として any です。これは、this がオブジェクト リテラル内の関数式に由来するためです。これを修正するには、明示的な this パラメーターを指定できます。this パラメーターは、関数のパラメーター リストの先頭にある偽のパラメーターです。

ts
function f(this: void) {
// make sure `this` is unusable in this standalone function
}

上記の例に、Card および Deck の 2 つのインターフェイスを追加して、型をより明確にし、再利用しやすくしましょう。

ts
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();
 
alert("card: " + pickedCard.card + " of " + pickedCard.suit);
Try

これで、TypeScript は createCardPickerDeck オブジェクトで呼び出されることを予期していることを認識します。つまり、thisany ではなく、型が Deck になったため、noImplicitThis によってエラーが発生することはありません。

コールバックでの this パラメーター

また、後で呼び出すライブラリに関数を渡す場合、コールバックで this を使用してエラーが発生する可能性もあります。コールバックを呼び出すライブラリは通常の関数のように呼び出すため、thisundefined になります。多少の作業で this パラメーターを使用して、コールバックでのエラーを防ぐこともできます。まず、ライブラリの作成者は、コールバック型に this を注釈する必要があります。

ts
interface UIElement {
addClickListener(onclick: (this: void, e: Event) => void): void;
}
Try

this: void は、addClickListeneronclickthis 型を必要としない関数であることを予期していることを意味します。次に、呼び出しコードに this を注釈します。

ts
class Handler {
info: string;
onClickBad(this: Handler, e: Event) {
// oops, used `this` here. using this callback would crash at runtime
this.info = e.message;
}
}
 
let h = new Handler();
uiElement.addClickListener(h.onClickBad); // error!
Argument of type '(this: Handler, e: Event) => void' is not assignable to parameter of type '(this: void, e: Event) => void'. The 'this' types of each signature are incompatible. Type 'void' is not assignable to type 'Handler'.2345Argument of type '(this: Handler, e: Event) => void' is not assignable to parameter of type '(this: void, e: Event) => void'. The 'this' types of each signature are incompatible. Type 'void' is not assignable to type 'Handler'.
Try

this を注釈すると、onClickBadHandler のインスタンスで呼び出す必要があることを明示的にします。次に、TypeScript は addClickListenerthis: void を持つ関数を必要とすることを検出します。エラーを修正するには、this の型を変更します。

ts
class Handler {
info: string;
onClickGood(this: void, e: Event) {
// can't use `this` here because it's of type void!
console.log("clicked!");
}
}
 
let h = new Handler();
uiElement.addClickListener(h.onClickGood);
Try

onClickGood はその this 型を void として指定しているため、addClickListener に渡すことは合法です。もちろん、これは this.info を使用できないことも意味します。両方が必要な場合は、アロー関数を使用する必要があります。

ts
class Handler {
info: string;
onClickGood = (e: Event) => {
this.info = e.message;
};
}
Try

これは、アロー関数が外側のthisを使用するため、this: voidを期待するものに常に渡せるためです。欠点は、Handler型のオブジェクトごとに1つのアロー関数が作成されることです。一方、メソッドは一度だけ作成され、Handlerのプロトタイプにアタッチされます。それらはHandler型のすべてのオブジェクト間で共有されます。

オーバーロード

JavaScriptは本質的に非常に動的な言語です。単一のJavaScript関数が、渡された引数の形状に基づいて異なる型のオブジェクトを返すことは珍しくありません。

ts
let suits = ["hearts", "spades", "clubs", "diamonds"];
 
function pickCard(x: any): any {
// Check to see if we're working with an object/array
// if so, they gave us the deck and we'll pick the card
if (typeof x == "object") {
let pickedCard = Math.floor(Math.random() * x.length);
return pickedCard;
}
// Otherwise just let them pick the card
else if (typeof x == "number") {
let pickedSuit = Math.floor(x / 13);
return { suit: suits[pickedSuit], card: x % 13 };
}
}
 
let myDeck = [
{ suit: "diamonds", card: 2 },
{ suit: "spades", card: 10 },
{ suit: "hearts", card: 4 },
];
 
let pickedCard1 = myDeck[pickCard(myDeck)];
alert("card: " + pickedCard1.card + " of " + pickedCard1.suit);
 
let pickedCard2 = pickCard(15);
alert("card: " + pickedCard2.card + " of " + pickedCard2.suit);
Try

ここで、pickCard関数は、ユーザーが渡した内容に基づいて2つの異なるものを返します。ユーザーがデッキを表すオブジェクトを渡した場合、関数はカードを引きます。ユーザーがカードを選んだ場合、どのカードを選んだかを伝えます。しかし、これを型システムにどのように記述すればよいでしょうか?

その答えは、同じ関数に対して、オーバーロードのリストとして複数の関数型を提供することです。このリストは、コンパイラーが関数呼び出しを解決するために使用します。pickCardが受け入れるものと返すものを記述するオーバーロードのリストを作成しましょう。

ts
let suits = ["hearts", "spades", "clubs", "diamonds"];
 
function pickCard(x: { suit: string; card: number }[]): number;
function pickCard(x: number): { suit: string; card: number };
function pickCard(x: any): any {
// Check to see if we're working with an object/array
// if so, they gave us the deck and we'll pick the card
if (typeof x == "object") {
let pickedCard = Math.floor(Math.random() * x.length);
return pickedCard;
}
// Otherwise just let them pick the card
else if (typeof x == "number") {
let pickedSuit = Math.floor(x / 13);
return { suit: suits[pickedSuit], card: x % 13 };
}
}
 
let myDeck = [
{ suit: "diamonds", card: 2 },
{ suit: "spades", card: 10 },
{ suit: "hearts", card: 4 },
];
 
let pickedCard1 = myDeck[pickCard(myDeck)];
alert("card: " + pickedCard1.card + " of " + pickedCard1.suit);
 
let pickedCard2 = pickCard(15);
alert("card: " + pickedCard2.card + " of " + pickedCard2.suit);
Try

この変更により、オーバーロードはpickCard関数への型チェックされた呼び出しを提供するようになりました。

コンパイラーが正しい型チェックを選択するために、基盤となるJavaScriptと同様のプロセスに従います。オーバーロードリストを見て、最初から順に、提供されたパラメーターで関数を呼び出そうとします。一致するものが見つかった場合、このオーバーロードを正しいオーバーロードとして選択します。このため、オーバーロードを最も具体的なものから最も具体性の低いものへと順序付けするのが慣例です。

function pickCard(x): anyの部分はオーバーロードリストの一部ではないため、オブジェクトを受け取るものと数値を受け取るものの2つのオーバーロードしかありません。他のパラメーター型でpickCardを呼び出すとエラーが発生します。

TypeScriptドキュメントはオープンソースプロジェクトです。これらのページの改善にご協力ください。プルリクエストを送信してください❤

このページの貢献者
RCRyan Cavanaugh (55)
DRDaniel Rosenwasser (23)
OTOrta Therox (18)
NSNathan Shively-Sanders (4)
MFMartin Fischer (1)
24+

最終更新日: 2024年3月21日