著者はCOVID-19救済基金を選択し、 Write forDOnationsプログラムの一環として寄付を受け取りました。

序章

関数の作成と使用はプログラミング言語の基本的な側面であり、TypeScriptも例外ではありません。 TypeScriptは、関数の既存の JavaScript構文を完全にサポートし、型情報および関数オーバーロードを新機能として追加します。 関数に追加のドキュメントを提供するだけでなく、型情報は、無効なデータ型を型セーフ関数に渡すリスクが低いため、コードのバグの可能性を減らします。

このチュートリアルでは、タイプ情報を使用して最も基本的な関数を作成することから始め、RESTパラメーターや関数のオーバーロードの使用などのより複雑なシナリオに進みます。 さまざまなコードサンプルを試してみてください。これらは、独自のTypeScript環境、またはブラウザで直接TypeScriptを記述できるオンライン環境である TypeScriptPlaygroundで実行できます。

前提条件

このチュートリアルに従うには、次のものが必要です。

  • TypeScriptプログラムを実行して、例に従うことができる環境。 これをローカルマシンに設定するには、次のものが必要です。 TypeScript関連のパッケージを処理する開発環境を実行するためにインストールされたNodeとnpm(またはyarn)の両方。 このチュートリアルは、Node.jsバージョン14.3.0およびnpmバージョン6.14.5でテストされました。 macOSまたはUbuntu18.04にインストールするには、「Node.jsをインストールしてmacOSにローカル開発環境を作成する方法」または「Ubuntu18.04にNode.jsをインストールする方法」の「PPAを使用したインストール」セクションの手順に従います。 これは、Windows Subsystem for Linux(WSL)を使用している場合にも機能します。 さらに、TypeScriptコンパイラ(tsc)がマシンにインストールされている必要があります。 これを行うには、TypeScriptの公式Webサイトを参照してください。
  • ローカルマシン上にTypeScript環境を作成したくない場合は、公式の TypeScriptPlaygroundを使用してフォローできます。
  • JavaScript、特に destructuring、rest演算子 imports /exportsなどのES6+構文に関する十分な知識が必要です。 これらのトピックに関する詳細情報が必要な場合は、JavaScriptシリーズのコーディング方法を読むことをお勧めします。
  • このチュートリアルでは、TypeScriptをサポートし、インラインエラーを表示するテキストエディタの側面を参照します。 これはTypeScriptを使用するために必要ではありませんが、TypeScript機能をさらに活用します。 これらの利点を活用するには、 Visual Studio Code のようなテキストエディターを使用できます。このエディターは、TypeScriptをすぐにサポートします。 TypeScriptPlaygroundでこれらの利点を試すこともできます。

このチュートリアルに示されているすべての例は、TypeScriptバージョン4.2.2を使用して作成されています。

型付き関数の作成

このセクションでは、TypeScriptで関数を作成し、それらに型情報を追加します。

JavaScriptでは、関数はさまざまな方法で宣言できます。 最も人気のあるものの1つは、次のようにfunctionキーワードを使用することです。

function sum(a, b) {
  return a + b;
}

この例では、sumは関数の名前、(a, b)は引数、{return a + b;}は関数本体です。

TypeScriptで関数を作成するための構文は、1つの主要な追加を除いて同じです。各引数またはパラメーターが持つべきタイプをコンパイラーに知らせることができます。 次のコードブロックは、このための一般的な構文を示しており、型宣言が強調表示されています。

function functionName(param1: Param1Type, param2: Param2Type): ReturnType {
  // ... body of the function
}

この構文を使用して、前に示したsum関数のパラメーターに型を追加できます。

function sum(a: number, b: number) {
  return a + b;
}

これにより、abnumberの値になります。

戻り値のタイプを追加することもできます。

function sum(a: number, b: number): number {
  return a + b;
}

これで、TypeScriptはsum関数が数値を返すことを期待します。 いくつかのパラメーターを使用して関数を呼び出し、結果値をresultという変数に格納する場合:

const result = sum(1, 2);

result変数のタイプはnumberになります。 TypeScriptプレイグラウンドを使用している場合、またはTypeScriptを完全にサポートするテキストエディタを使用している場合、カーソルでresultにカーソルを合わせると、const result: numberが表示され、TypeScriptが関数宣言からそのタイプを暗示していることが示されます。

関数が期待するタイプ以外のタイプの値で関数を呼び出した場合、TypeScriptコンパイラ(tsc)はエラー2345を返します。 sum関数を次のように呼び出します。

sum('shark', 'whale');

これにより、次のようになります。

Output
Argument of type 'string' is not assignable to parameter of type 'number'. (2345)

基本タイプだけでなく、関数で任意のタイプを使用できます。 たとえば、次のようなUserタイプがあるとします。

type User = {
  firstName: string;
  lastName: string;
};

次のように、ユーザーのフルネームを返す関数を作成できます。

function getUserFullName(user: User): string {
  return `${user.firstName} ${user.lastName}`;
}

ほとんどの場合、TypeScriptは関数の戻り型を推測するのに十分賢いので、この場合は関数宣言から戻り型を削除できます。

function getUserFullName(user: User) {
  return `${user.firstName} ${user.lastName}`;
}

関数の戻り型である: string部分を削除したことに注意してください。 関数の本体で文字列を返すため、TypeScriptは関数が文字列を返すタイプであると正しく想定します。

今すぐ関数を呼び出すには、Userタイプと同じ形状のオブジェクトを渡す必要があります。

type User = {
  firstName: string;
  lastName: string;
};

function getUserFullName(user: User) {
  return `${user.firstName} ${user.lastName}`;
}

const user: User = {
  firstName: "Jon",
  lastName: "Doe"
};

const userFullName = getUserFullName(user);

このコードは、TypeScriptタイプチェッカーに正常に合格します。 エディターでuserFullName定数にカーソルを合わせると、エディターはそのタイプをstringとして識別します。

TypeScriptのオプションの関数パラメータ

関数を作成するときに、すべてのパラメーターを用意する必要はありません。 このセクションでは、TypeScriptで関数パラメーターをオプションとしてマークする方法を学習します。

関数パラメーターをオプションにするには、パラメーター名の直後に?修飾子を追加します。 タイプTの関数パラメーターparam1が与えられた場合、以下で強調表示されているように、?を追加することにより、param1をオプションのパラメーターにすることができます。

param1?: T

たとえば、オプションのprefixパラメーターをgetUserFullName関数に追加します。これは、ユーザーのフルネームのプレフィックスとして追加できるオプションの文字列です。

type User = {
  firstName: string;
  lastName: string;
};

function getUserFullName(user: User, prefix?: string) {
  return `${prefix ?? ''}${user.firstName} ${user.lastName}`;
}

このコードブロックの最初の強調表示された部分では、オプションのprefixパラメーターを関数に追加し、2番目の強調表示された部分では、ユーザーのフルネームの前にそれを付けています。 これを行うには、nullish合体演算子??を使用しています。 このように、prefix値が定義されている場合にのみ使用します。 それ以外の場合、関数は空の文字列を使用します。

これで、次のように、プレフィックスパラメータの有無にかかわらず関数を呼び出すことができます。

type User = {
  firstName: string;
  lastName: string;
};

function getUserFullName(user: User, prefix?: string) {
  return `${prefix ?? ''} ${user.firstName} ${user.lastName}`;
}

const user: User = {
  firstName: "Jon",
  lastName: "Doe"
};

const userFullName = getUserFullName(user);
const mrUserFullName = getUserFullName(user, 'Mr. ');

この場合、userFullNameの値はJon Doeになり、mrUserFullNameの値はMr. Jon Doeになります。

必須パラメーターの前にオプションのパラメーターを追加することはできないことに注意してください。 (user: User, prefix?: string)の場合と同様に、シリーズの最後にリストする必要があります。 最初にリストすると、TypeScriptコンパイラはエラー1016を返します。

Output
A required parameter cannot follow an optional parameter. (1016)

型付き矢印関数式

これまでのところ、このチュートリアルでは、functionキーワードで定義されたTypeScriptで通常の関数を入力する方法を示してきました。 ただし、JavaScriptでは、矢印関数など、複数の方法で関数を定義できます。 このセクションでは、TypeScriptの矢印関数にタイプを追加します。

矢印関数に型を追加するための構文は、通常の関数に型を追加するのとほとんど同じです。 これを説明するために、getUserFullName関数を矢印関数式に変更します。

const getUserFullName = (user: User, prefix?: string) => `${prefix ?? ''}${user.firstName} ${user.lastName}`;

関数の戻り値の型を明示したい場合は、次のブロックで強調表示されているコードに示すように、()の後に追加します。

const getUserFullName = (user: User, prefix?: string): string => `${prefix ?? ''}${user.firstName} ${user.lastName}`;

これで、以前とまったく同じように関数を使用できます。

type User = {
  firstName: string;
  lastName: string;
};

const getUserFullName = (user: User, prefix?: string) => `${prefix ?? ''}${user.firstName} ${user.lastName}`;

const user: User = {
  firstName: "Jon",
  lastName: "Doe"
};

const userFullName = getUserFullName(user);

これにより、TypeScriptタイプチェッカーがエラーなしで渡されます。

注: JavaScriptの関数に有効なものはすべて、TypeScriptの関数にも有効であることに注意してください。 これらのルールの復習については、JavaScriptで関数を定義する方法チュートリアルをご覧ください。

関数型

前のセクションでは、TypeScriptの関数のパラメーターと戻り値にタイプを追加しました。 このセクションでは、特定の関数シグネチャを表す型である関数型を作成する方法を学習します。 特定の関数に一致する型を作成することは、それ自体が関数であるパラメーターを持つなど、関数を他の関数に渡すときに特に役立ちます。 これは、コールバックを受け入れる関数を作成するときの一般的なパターンです。

関数型を作成するための構文は、矢印関数の作成と似ていますが、2つの違いがあります。

  • 関数本体を削除します。
  • 関数宣言でreturnタイプ自体を返すようにします。

使用しているgetUserFullName関数に一致する型を作成する方法は次のとおりです。

type User = {
  firstName: string;
  lastName: string;
};

type PrintUserNameFunction = (user: User, prefix?: string) => string;

この例では、typeキーワードを使用して新しい型を宣言し、括弧内の2つのパラメーターの型と、矢印の後の戻り値の型を指定しました。

より具体的な例として、イベントリスナー関数を作成しているとします。これはonEventと呼ばれ、最初のパラメーターとしてイベント名を受け取り、2番目のパラメーターとしてイベントコールバックを受け取ります。 イベントコールバック自体は、最初のパラメータとして次のタイプのオブジェクトを受け取ります。

type EventContext = {
  value: string;
};

次に、onEvent関数を次のように記述できます。

type EventContext = {
  value: string;
};

function onEvent(eventName: string, eventCallback: (target: EventContext) => void) {
  // ... implementation
}

eventCallbackパラメーターの型が関数型であることに注意してください。

eventCallback: (target: EventTarget) => void

これは、onEvent関数が、eventCallbackパラメーターで渡される別の関数を予期していることを意味します。 この関数は、タイプEventTargetの単一の引数を受け入れる必要があります。 この関数の戻り型はonEvent関数によって無視されるため、型としてvoidを使用しています。

型付き非同期関数の使用

JavaScriptを使用する場合、非同期関数を使用するのが比較的一般的です。 TypeScriptには、これに対処するための特定の方法があります。 このセクションでは、TypeScriptで非同期関数を作成します。

非同期関数を作成するための構文は、JavaScriptで使用される構文と同じですが、許可タイプが追加されています。

async function asyncFunction(param1: number) {
  // ... function implementation ...
}

通常の関数に型を追加することと非同期関数に型を追加することには、大きな違いが1つあります。非同期関数では、戻り型は常にPromise<T>ジェネリックである必要があります。 Promise<T>ジェネリックは、非同期関数によって返されるPromiseオブジェクトを表します。ここで、Tは、promiseが解決される値のタイプです。

Userタイプがあるとします。

type User = {
  id: number;
  firstName: string;
};

また、データストアにいくつかのユーザーオブジェクトがあるとします。 このデータは、ファイル、データベース、またはAPIリクエストの背後など、どこにでも保存できます。 簡単にするために、この例ではarrayを使用します。

type User = {
  id: number;
  firstName: string;
};

const users: User[] = [
  { id: 1, firstName: "Jane" },
  { id: 2, firstName: "Jon" }
];

IDによってユーザーを非同期的に取得するタイプセーフな関数を作成する場合は、次のように実行できます。

async function getUserById(userId: number): Promise<User | null> {
  const foundUser = users.find(user => user.id === userId);

  if (!foundUser) {
    return null;
  }

  return foundUser;
}

この関数では、最初に関数を非同期として宣言しています。

async function getUserById(userId: number): Promise<User | null> {

次に、最初のパラメーターとしてユーザーIDを受け入れることを指定します。ユーザーIDはnumberである必要があります。

async function getUserById(userId: number): Promise<User | null> {

getUserByIdの戻りタイプは、Userまたはnullのいずれかに解決されるPromiseです。 共用体型User | nullを、Promiseジェネリックの型パラメーターとして使用しています。

User | nullは、Promise<T>Tです。

async function getUserById(userId: number): Promise<User | null> {

awaitを使用して関数を呼び出し、結果をuserという変数に格納します。

type User = {
  id: number;
  firstName: string;
};

const users: User[] = [
  { id: 1, firstName: "Jane" },
  { id: 2, firstName: "Jon" }
];

async function getUserById(userId: number): Promise<User | null> {
  const foundUser = users.find(user => user.id === userId);

  if (!foundUser) {
    return null;
  }

  return foundUser;
}

async function runProgram() {
  const user = await getUserById(1);
}

注:ファイルの最上位でawaitを使用できないため、runProgramというラッパー関数を使用しています。 これを行うと、TypeScriptコンパイラがエラー1375を発行します。

Output
'await' expressions are only allowed at the top level of a file when that file is a module, but this file has no imports or exports. Consider adding an empty 'export {}' to make this file a module. (1375)

エディターまたはTypeScriptPlaygroundでuserにカーソルを合わせると、userのタイプがUser | nullであることがわかります。これは、まさにあなたが返す約束のタイプです。 getUserById関数はに解決されます。

awaitを削除し、関数を直接呼び出すと、Promiseオブジェクトが返されます。

async function runProgram() {
  const userPromise = getUserById(1);
}

userPromiseにカーソルを合わせると、タイプがPromise<User | null>であることがわかります。

ほとんどの場合、TypeScriptは、非非同期関数の場合と同じように、非同期関数の戻り型を推測できます。 したがって、getUserById関数の戻り型は、Promise<User | null>型であると正しく推測されるため、省略できます。

async function getUserById(userId: number) {
  const foundUser = users.find(user => user.id === userId);

  if (!foundUser) {
    return null;
  }

  return foundUser;
}

残りのパラメーターへのタイプの追加

Restパラメーターは、関数が単一の配列として多くのパラメーターを受け取ることを可能にするJavaScriptの機能です。 このセクションでは、TypeScriptでRESTパラメーターを使用します。

タイプセーフな方法でRESTパラメーターを使用するには、RESTパラメーターの後に結果の配列のタイプを使用することで完全に可能です。 たとえば、次のコードを考えてみましょう。ここでは、sumという関数があり、可変量の数値を受け取り、それらの合計を返します。

function sum(...args: number[]) {
  return args.reduce((accumulator, currentValue) => {
    return accumulator + currentValue;
  }, 0);
}

この関数は、 .reduce Arrayメソッドを使用して配列を反復処理し、要素を一緒に追加します。 ここで強調表示されている残りのパラメーターargsに注意してください。 タイプは数値の配列number[]に設定されています。

関数の呼び出しは正常に機能します。

function sum(...args: number[]) {
  return args.reduce((accumulator, currentValue) => {
    return accumulator + currentValue;
  }, 0);
}

const sumResult = sum(2, 4, 6, 8);

次のように、数値以外のものを使用して関数を呼び出す場合:

const sumResult = sum(2, "b", 6, 8);

TypeScriptコンパイラはエラー2345を発行します:

Output
Argument of type 'string' is not assignable to parameter of type 'number'. (2345)

関数のオーバーロードの使用

プログラマーは、関数の呼び出し方法に応じて、さまざまなパラメーターを受け入れる関数が必要になる場合があります。 JavaScriptでは、これは通常、文字列や数値など、さまざまなタイプの値を想定する可能性のあるパラメーターを持つことによって行われます。 複数の実装を同じ関数名に設定することを関数オーバーロードと呼びます。

TypeScriptを使用すると、対処するさまざまなケースを明示的に記述する関数オーバーロードを作成でき、オーバーロードされた関数の各実装を個別に文書化することで、開発者のエクスペリエンスを向上させます。 このセクションでは、TypeScriptで関数のオーバーロードを使用する方法について説明します。

Userタイプがあるとします。

type User = {
  id: number;
  email: string;
  fullName: string;
  age: number;
};

そして、次の情報のいずれかを使用してユーザーを検索できる関数を作成するとします。

  • id
  • email
  • ageおよびfullName

次のような関数を作成できます。

function getUser(idOrEmailOrAge: number | string, fullName?: string): User | undefined {
  // ... code
}

この関数は、|演算子を使用して、idOrEmailOrAgeと戻り値の型の共用体を構成します。

次に、次の強調表示されたコードに示すように、関数を使用する方法ごとに関数のオーバーロードを追加します。

type User = {
  id: number;
  email: string;
  fullName: string;
  age: number;
};

function getUser(id: number): User | undefined;
function getUser(email: string): User | undefined;
function getUser(age: number, fullName: string): User | undefined;

function getUser(idOrEmailOrAge: number | string, fullName?: string): User | undefined {
  // ... code
}

この関数には3つのオーバーロードがあり、ユーザーを取得する方法ごとに1つずつあります。 関数のオーバーロードを作成するときは、関数の実装自体の前に関数のオーバーロードを追加します。 関数のオーバーロードには本体がありません。 パラメータのリストとリターンタイプだけがあります。

次に、関数自体を実装します。これには、すべての関数のオーバーロードと互換性のあるパラメーターリストが必要です。 前の例では、最初のパラメーターはidemail、またはageである可能性があるため、数値または文字列のいずれかになります。

function getUser(id: number): User | undefined;
function getUser(email: string): User | undefined;
function getUser(age: number, fullName: string): User | undefined;

function getUser(idOrEmailOrAge: number | string, fullName?: string): User | undefined {
  // ... code
}

したがって、関数実装のidOrEmailorAgeパラメーターのタイプをnumber | stringに設定します。 このように、getUser関数のすべてのオーバーロードと互換性があります。

また、ユーザーがfullNameを渡す場合に備えて、関数にオプションのパラメーターを追加しています。

function getUser(id: number): User | undefined;
function getUser(email: string): User | undefined;
function getUser(age: number, fullName: string): User | undefined;

function getUser(idOrEmailOrAge: number | string, fullName?: string): User | undefined {
  // ... code
}

関数の実装は次のようになります。ここでは、users配列をユーザーのデータストアとして使用しています。

type User = {
  id: number;
  email: string;
  fullName: string;
  age: number;
};

const users: User[] = [
  { id: 1, email: "[email protected]", fullName: "Jane Doe" , age: 35 },
  { id: 2, email: "[email protected]", fullName: "Jon Doe", age: 35 }
];

function getUser(id: number): User | undefined;
function getUser(email: string): User | undefined;
function getUser(age: number, fullName: string): User | undefined;

function getUser(idOrEmailOrAge: number | string, fullName?: string): User | undefined {
  if (typeof idOrEmailOrAge === "string") {
    return users.find(user => user.email === idOrEmailOrAge);
  }

  if (typeof fullName === "string") {
    return users.find(user => user.age === idOrEmailOrAge && user.fullName === fullName);
  } else {
    return users.find(user => user.id === idOrEmailOrAge);
  }
}

const userById = getUser(1);
const userByEmail = getUser("[email protected]");
const userByAgeAndFullName = getUser(35, "Jon Doe");

このコードでは、idOrEmailOrAgeが文字列の場合、emailキーを使用してユーザーを検索できます。 次の条件は、idOrEmailOrAgeが数値であることを前提としているため、fullNameが定義されているかどうかに応じて、idまたはageのいずれかになります。

関数のオーバーロードの興味深い側面の1つは、VSCodeやTypeScriptPlaygroundを含むほとんどのエディターで、関数名を入力し、最初の括弧を開いて関数を呼び出すとすぐに、使用可能なすべてのオーバーロードを含むポップアップが表示されることです。次の画像に示すように:

Overloads Popup

各関数のオーバーロードにコメントを追加すると、そのコメントはドキュメントのソースとしてポップアップにも表示されます。 たとえば、次の強調表示されたコメントをオーバーロードの例に追加します。

...
/**
 * Get a user by their ID.
 */
function getUser(id: number): User | undefined;
/**
 * Get a user by their email.
 */
function getUser(email: string): User | undefined;
/**
 * Get a user by their age and full name.
 */
function getUser(age: number, fullName: string): User | undefined;
...

これらの関数にカーソルを合わせると、次のアニメーションに示すように、オーバーロードごとにコメントが表示されます。

Overloads Popup with Comments

ユーザー定義のタイプガード

このチュートリアルで検討するTypeScriptの関数の最後の機能は、ユーザー定義の型ガードです。これは、TypeScriptが値の型をより適切に推測できるようにする特別な関数です。 これらのガードは、条件付きコードブロックで特定のタイプを強制します。この場合、値のタイプは状況によって異なる場合があります。 これらは、Array.prototype.filter関数を使用してフィルター処理されたデータの配列を返す場合に特に役立ちます。

配列に条件付きで値を追加する場合の一般的なタスクの1つは、いくつかの条件をチェックし、条件が真の場合にのみ値を追加することです。 値がtrueでない場合、コードはfalse Booleanを配列に追加します。 その配列を使用する前に、.filter(Boolean)を使用して配列をフィルタリングし、真の値のみが返されるようにすることができます。

ブールコンストラクターは、値を指定して呼び出されると、この値がTruthyまたはFalsyのどちらの値であるかに応じて、trueまたはfalseを返します。

たとえば、文字列の配列があり、他のフラグがtrueの場合にのみ、文字列productionをその配列に含めたいとします。

const isProduction = false

const valuesArray = ['some-string', isProduction && 'production']

function processArray(array: string[]) {
  // do something with array
}

processArray(valuesArray.filter(Boolean))

これは実行時に完全に有効なコードですが、TypeScriptコンパイラはコンパイル中にエラー2345を表示します。

Output
Argument of type '(string | boolean)[]' is not assignable to parameter of type 'string[]'. Type 'string | boolean' is not assignable to type 'string'. Type 'boolean' is not assignable to type 'string'. (2345)

このエラーは、コンパイル時に、processArrayに渡された値が、false | string値の配列として解釈されることを示しています。これは、processArrayが期待したものではありません。 文字列の配列string[]が必要です。

これは、TypeScriptが.filter(Boolean)を使用して、配列からすべてのfalsy値を削除していると推測できるほど賢くない場合の1つです。 ただし、このヒントをTypeScriptに与える方法は1つあります。それは、ユーザー定義の型ガードを使用することです。

isStringと呼ばれるユーザー定義のタイプガード関数を作成します。

function isString(value: any): value is string {
  return typeof value === "string"
}

isString関数の戻りタイプに注意してください。 ユーザー定義型ガードを作成する方法は、関数の戻り型として次の構文を使用することです。

parameterName is Type

ここで、parameterNameはテストしているパラメーターの名前であり、Typeは、この関数がtrueを返す場合にこのパラメーターの値が期待するタイプです。

この場合、isStringtrueを返す場合、valuestringであると言っています。 また、valueパラメーターのタイプをanyに設定しているため、anyタイプの値で機能します。

ここで、.filter呼び出しを変更して、Booleanコンストラクターを渡す代わりに、新しい関数を使用します。

const isProduction = false

const valuesArray = ['some-string', isProduction && 'production']

function processArray(array: string[]) {
  // do something with array
}

function isString(value: any): value is string {
  return typeof value === "string"
}

processArray(valuesArray.filter(isString))

これで、TypeScriptコンパイラは、processArrayに渡された配列に文字列のみが含まれていることを正しく推測し、コードが正しくコンパイルされます。

結論

関数はTypeScriptのアプリケーションの構成要素であり、このチュートリアルでは、TypeScriptでタイプセーフな関数を作成する方法と、関数のオーバーロードを利用して単一の関数のすべてのバリアントをより適切に文書化する方法を学びました。 この知識があれば、コード全体でよりタイプセーフで保守が容易な関数が可能になります。

TypeScriptのその他のチュートリアルについては、TypeScriptシリーズのコーディング方法ページをご覧ください。