筆者がTypeScriptを学ぶ上で読んだ プログラミングTypeScript スケールするJavaScriptアプリケーション開発」を読んで終わりにしないために、記事という形でアウトプットしたかったので投稿します。間違っているところや足りないところがあったら遠慮なく突っ込んでください!
本投稿は3本目です。前回の記事のリンクを貼っておきますのでよければ見てみてください。
〜 じっくり入門 TypeScript 〜 2. 型について
それではいきましょう。
基本的な関数宣言
TypeScriptの関数宣言はJavaScriptと大きく変わらないです。決定的な違いは関数のパラメータ
と戻り値に型アノテーションをつけることができることでしょう。
ちなみに、用語的な違いとして以下があります。
パラメータ : 関数が実行されるために必要なデータ(仮パラメータとも呼ぶ)
引数:関数を呼び出す時に渡すデータ(実パラメータとも呼ぶ)
TypeScriptは多くの場合、型を推論できますが、関数宣言時のパラメータは推論しません。なので、型アノテーションを付けてあげる必要があります。戻り値は推論できるので型アノテーションは省略できます。
それでは実際に関数の宣言方法をみてみましょう。
// 名前付き関数
function greet(name: string) {
return 'Hello' + name;
}
// 関数式
let greet2 = function(name: string) {
return 'Hello' + name;
}
// アロー関数式
let greet3 = (name: string) => {
return 'Hello' + name;
}
// アロー関数式の省略記法
let greet4 = (name: string) => 'Hello' + name;
様々な種類のパラメータ
関数のパラメータには様々な種類があります。まずはオプションパラメータから記載します。
オプションパラメータ
パラメータの宣言時に?
をつけることで、存在しても、しなくても良いパラメータを定義することができる。オプションパラメータはそのほかのパラメータよりも後ろで宣言しなければならない。
// log関数の呼び出し時にuserIdは存在しても、しなくても良いパラメータ
function log(message: string, userId?: string) {
let time = new Date().toLocaleTimeString();
console.log(time, message, userId);
}
log('user signed in', 'sssfkdoe')
// userIdパラメータを引数として渡さなくてもエラーにならずに実行できる
log('page loaded');
// エラー:必須パラメーターを省略可能なパラメーターの後に指定することはできません。
function log2(message?: string, userId: string) {
let time = new Date().toLocaleTimeString();
console.log(time, message, userId);
}
デフォルトパラメータ
初期値を持ったパラメータのこと。実行時に引数を渡すことで、置き換えることもできる。
また、与えられたデフォルトパラメータから型を推論するため型アノテーションは不要になります。もちろん、型アノテーションを記述することもできますが、多くの場合は省略します。
デフォルトパラメータをもった関数は実行時に引数を与えなくても良いという点でオプションパラメータと同じですが、オプションパラメータは、ほかのパラメータよりも後ろで宣言しなければならなかったのに対して、デフォルトパラメータは順序を問わず宣言することができます。
// userId に 'not signed in'という初期値を与えている
function log(message: string, userId = 'not signed in') {
let time = new Date().toLocaleTimeString();
console.log(time, message, userId);
}
log('page loaded'); // OK
log('user signed in', 'sssfkdoe') // OK
// デフォルトパラメータは順序関係なく宣言することができる
function log2(userId = 'not signed in', message: string) {
let time = new Date().toLocaleTimeString();
console.log(time, message, userId);
}
// アノテーションを記述することもできる
type Content = {
title?: string,
author?: string,
month?: number
}
function logger(message: string, userId: Content = {}) {
let time = new Date().toLocaleTimeString();
console.log(time, message, userId);
}
レストパラメータ
レストパラメータとは可変長引数を関数宣言時に与える時の呼び方です。関数のパラメータの中に1つしか持つことができず、パラメータ宣言の一番最後に行うのがルールです。
// 引数がいくつあるかわからないが、1つ以上のnumber型の引数をとる関数
function sumValue(...numbers: number[]): number {
return Array
.from(numbers)
.reduce((total, n) => total + n)
}
sumValue(1, 2, 3); // 6
ジェネレーター ・ ジェネレーター関数
ジェネレーターとはとは読んで字のごとく、値を生成する方法のことです。それを行う関数のことを、ジェネレーター関数と呼びます。ジェネレーター関数は逐次実行されることはなく、ユーザーからの呼び出しに反応して次の値を返すようになっています。
// function* と宣言することによってジェネレーター関数になる
function* fibonacchi() {
let a = 0;
let b = 1;
while(true) {
yield a;
}
[a, b] = [b, a + b];
}
通常の関数であれば一度呼び出したら無限に実行されてしまう関数ですが、ジェネレーター関数は違います。ユーザーからの呼び出しで一度、値(ジェネレータ)を返したら、yield
キーワードがあることでユーザーの次の要求があるまで休止します。
ジェネレーター関数のnext
を呼び出すたびにその値は生成されます。
// 先ほど宣言したジェネレーター関数
let generator = fibonacchi();
generator.next(); // 0
generator.next(); // 1
generator.next(); // 1
generator.next(); // 2
generator.next(); // 3
generator.next(); // 5
また、TypeScriptはyield
で生成される値の型から、ジェネレーター関数が生成する型を推論することができます。なので、戻り値の型アノテーションは省略することができます。明示的にジェネレータの戻り値をアノテーションすることもできます。
// 戻り値は推論されるが、アノテーションすることも可能
function* fibonacchi() Generator<number> {
let a = 0;
let b = 1;
while(true) {
yield a;
}
[a, b] = [b, a + b];
}
呼び出しシグネチャ(型シグネチャ)
すごく簡単にまとめると関数の型のことです。それ自体に値は持たず、型だけで記載されているものを指します。また、呼び出しシグネチャはそれ自体に値を持たないので、戻り値にも明示的なアノテーションが必要です。
type Log = (messgae: string, userId?: string) => void; // 戻り値にアノテーションが必要
let logFunciton: Log = (
messgae,
userId) => {
console.log(messgae, userId);
}
上記の例では、logFunciton関数
の引数にも、戻り値にもアノテーションがついていません。このように、型シグネチャを割り当てた関数は引数・戻り値どちらもアノテーションをする必要がありません。(割り当てられた型と異なるアノテーションをするとエラーになります)
また、型シグネチャ宣言時にはパラメータにデフォルトの値を設定できませんが、型シグネチャを割り当てた関数ではデフォルトの値をパラメータに割り当てることができます。
type Log = (messgae: string, userId?: string) => void;
// パラメータにデフォルト値を渡すことができる
let defaultLog: Log = (
messgae="default params",
userId) => {
console.log(messgae, userId);
}
オーバーロードされた関数シグネチャ
まず、オーバーロードされた関数とはどのようなものかを簡単に説明します。
オーバーロードされた関数
複数の呼び出しシグネチャを持つ関数のこと
簡単ですね。先ほど説明した、型シグネチャを複数持つ関数のことです。具体例をみてみましょう。
まず、複数の呼び出しシグネチャを持つオーバーロードされた関数を宣言してみます。
type Reserve = {
(from: Date, to: Date, destination: string): Reservation
(from: Date, destination: string): Reservation
}
旅行の予約をする機能を実装したいと考えてください。日帰りの場合もあれば、何泊かする場合があります。上記のReserve
はそのどちらにも対応できるオーバーロードされた関数です。戻り値はわかりやすいようにReservation
というすでに定義された型を返すとしましょう。
Reserve
を実際に割り当てて呼び出してみましょう。
let travel: Reserve = (from, to, destination) => {
return // something
}
// Error : 型 '(from: any, to: any, destination: any) => void'を型'Reserve'に割り当てることはできません。
Reserve
を宣言した側から見れば、2つの呼び出しシグネチャをもったオーバーロードされた関数と簡単にわかりますが、呼び出し側の観点からみた場合、Reserve
を手動で結合した結果を宣言する必要があります。推論されることはないのです。
let travel: Reserve = (
from: Date,
toOrDestination: string | Date,
destination? : string
) => {
return //
}
オーバーロードされた関数を割り当てた場合、上記例でいうとReserve
が持つどちらかの呼び出しシグネチャを利用することになります。なので、結合したパラメータを割り当て側では手動で宣言する必要があります。
最後に
TypeScriptにおける関数の宣言と呼び出しについて投稿しました。次回も関数に関して投稿する予定です。ご覧になっていただけるとありがたいです!