TypeScript | 基礎文法:関数の基礎 – 関数型エイリアス

TypeScript
スポンサーリンク

関数型エイリアスってそもそも何?

まず一言でいうと、関数型エイリアスは「関数の“形”に名前をつける仕組み」です。

「この関数は、こういう引数を受け取って、こういう型を返す」という“関数の型”に、わかりやすいラベルをつけて再利用できるようにするものです。

type Add = (a: number, b: number) => number;
TypeScript

これは、「number を2つ受け取って number を返す関数」を Add という名前で呼べるようにした、ということです。
以降は「(a: number, b: number) => number」と毎回書く代わりに、Add と書けば同じ意味になります。

関数が増えてきたり、あちこちで“同じ形の関数”を扱いたくなったとき、ここに効いてきます。


一番基本の形:関数の“型”に名前をつける

「関数の型」をエイリアスにする書き方

まず、関数型エイリアスの基本形をはっきりさせましょう。

type Greet = (name: string) => string;
TypeScript

この1行には、次の意味があります。

Greet という名前は、“name: string を引数に取り、string を返す関数型”の別名ですよ」

これを使うと、関数を書くときにこうできます。

const greet: Greet = (name) => {
  return `Hello, ${name}`;
};
TypeScript

ここでは greet: Greet と書くことで、

greet には、Greet 型(= string を受け取って string を返す関数)しか代入できない」

という制約を TypeScript に伝えています。
もし約束を破ろうとすると、ちゃんと怒ってくれます。

const greet: Greet = (name) => {
  return 123;  // エラー: Type 'number' is not assignable to type 'string'.
};
TypeScript

関数型エイリアスは、「こういう形の関数だけ入ってきてほしい」という“関数のインターフェース”を与えるものだと捉えてください。

同じ形の関数を何個も定義するときのメリット

例えば、「number を2つ受け取って number を返す関数」が何個も出てくる場合。

type BinaryNumberOp = (a: number, b: number) => number;

const add: BinaryNumberOp = (a, b) => a + b;
const sub: BinaryNumberOp = (a, b) => a - b;
const mul: BinaryNumberOp = (a, b) => a * b;
TypeScript

「足し算」「引き算」「掛け算」は、それぞれ実装は違うけれど、「関数としての形」は同じです。
その“形”を BinaryNumberOp という一つの型エイリアスにしておくことで、

コードの見通しが良くなり
型定義の重複がなくなり
「この3つは同じレベルの操作なんだな」という関係性も伝わる

という状態になります。


関数型エイリアスを引数に使う(コールバックの設計)

「こういう関数を渡してね」と型で指定する

関数型エイリアスが一番よく活躍するのは、「関数を引数に取る関数」を書くときです。

例えば、文字列を加工する処理を外から差し替えたい、という場面を考えます。

type StringTransformer = (value: string) => string;

function processLine(line: string, transform: StringTransformer) {
  const result = transform(line);
  console.log(result);
}
TypeScript

ここで StringTransformer は、「string を受け取って string を返す関数」という意味です。
processLine の第2引数 transform は、その型の関数だけを受け付けます。

使う側は、こんなふうに関数を渡します。

const toUpper: StringTransformer = (value) => value.toUpperCase();
const addPrefix: StringTransformer = (value) => "[LOG] " + value;

processLine("hello", toUpper);     // "HELLO"
processLine("hello", addPrefix);   // "[LOG] hello"
processLine("hello", (v) => v + "!"); // 直接アロー関数でもOK
TypeScript

ここで守られている約束は明確です。

transform は必ず string を受け取り
必ず string を返す

それ以外の形の関数は渡せません。
「どういう関数を渡してよくて、どういう関数はダメか」を、関数型エイリアスがはっきり線引きしてくれるイメージです。


オブジェクトの“メソッド”にも関数型エイリアスを使う

プロパティとしての関数の型をきれいに書く

オブジェクトの中に「関数のプロパティ」を持つときも、関数型エイリアスはよく使われます。

type LoggerFn = (message: string) => void;

type Logger = {
  info: LoggerFn;
  error: LoggerFn;
};

const logger: Logger = {
  info: (msg) => {
    console.log("[INFO]", msg);
  },
  error: (msg) => {
    console.error("[ERROR]", msg);
  },
};

logger.info("start");
logger.error("something went wrong");
TypeScript

ここでは、

LoggerFn … 「string を受け取って何も返さない関数」
Logger … その LoggerFninfoerror プロパティに持つオブジェクト

という関係になっています。

もし info の型だけ変えてしまうと、すぐに型エラーになります。

const badLogger: Logger = {
  info: (msg) => msg.length, // エラー: 戻り値が void ではない
  error: (msg) => console.error(msg),
};
TypeScript

「このオブジェクトにぶら下がっている関数たちは、全部この形ですよ」というルールを、関数型エイリアスで一括管理できるのが気持ちいいポイントです。


関数型エイリアスを設計するときの“思考の順番”

ここからが一番大事なところです。
syntax(記法)より、「どう考えるか」が肝です。

関数型エイリアスを書く前に、必ず自分にこう問いかけてください。

この関数は、何を受け取って
何を返す関数であるべきか?

例えば「API からデータを読み込む関数」を考えます。

ID(string)を1つ受け取り
非同期に User 型のデータを返す

頭の中では、きっとそうイメージしているはずです。
それをそのまま型に落とすとこうなります。

type LoadUser = (id: string) => Promise<User>;
TypeScript

これだけです。
この1行で、

LoadUser は、“string を受け取って Promise<User> を返す関数”という役割だ」

と宣言できました。

あとは、それを変数や引数にくっつけていくだけです。

const loadUserFromApi: LoadUser = async (id) => {
  const res = await fetch(`/api/users/${id}`);
  return res.json();
};

function showUserName(loadUser: LoadUser) {
  // ここでも同じ型を使い回せる
}
TypeScript

「まず“どういう関数が欲しいか”を言葉にして、それを (引数の型) => 戻り値の型 に変換する」
この繰り返しが、関数型エイリアス設計の本質です。


関数型エイリアスを使うべきタイミングと、その意味

最後に、「いつ関数型エイリアスを使うのが良いか」を感覚で掴んでほしいです。

同じ形の関数が何度も出てくるとき
→ その“形”に名前をつけてあげると、読み手にも伝わるし自分も楽になります。

「この関数はこういう役割を持っている」と、名前で表したいとき
StringTransformer, Predicate<T>, Loader, LoggerFn など、
役割を表す名前をつけることで、コードが“仕様書っぽく”なります。

関数を引数として受け渡しする箇所が増えてきたとき
→ 関数型エイリアスを挟むことで、「渡す側」「受け取る側」が同じ契約を見て話せるようになります。

そして一番大事なのは、
「type で名前をつけた瞬間、その関数型は“プロジェクトの言葉”になる」ということです。

(value: string) => string という無機質な型が、
type StringTransformer = (value: string) => string;
と名前を与えられた瞬間、「これは“文字列を変換する関数”なんだ」と意味を持ち始めます。

関数型エイリアスは、
ただの省略記法ではなく、「関数に役割と言葉を与えるための道具」だと捉えてみてください。
その感覚で使い始めると、急にコードが“自分の言葉で書かれている”ように感じられてきます。

タイトルとURLをコピーしました