06. any・unknown・型アサーション 08. 型の絞り込み 資料トップへ戻る
// 関数宣言
function add(a: number, b: number): number {
return a + b;
}
// アロー関数
const sub = (a: number, b: number): number => a - b;
// 型だけを定義する
type BinaryOp = (a: number, b: number) => number;
const mul: BinaryOp = (a, b) => a * b; // 引数の型は推論される
型の位置で使う矢印は =>、値としてのアロー関数も => です。
紛らわしいですが、型の文脈では戻り値の型を書き、値の文脈では処理を書きます。type BinaryOp = (a: number, b: number) => number; の number は「戻り値の型」です。
推論に任せられますが、公開する関数には書くことを勧めます。
| 判定 | 場面 | 理由 |
|---|---|---|
| 書く | 他のファイルから使われる関数 | 実装を変えたとき、変更の影響がその関数の中で止まる |
| 書く | 再帰する関数 | 推論できない場合がある |
| 省く | その場で使う短いコールバック | 書くとかえって読みにくい |
| 省く | 1行で値を返すだけの関数 | 推論で十分 |
// 書いておくと、実装ミスがこの関数の中で捕まる
function findName(id: string): string {
const users: Record<string, string> = { "1": "Alice" };
return users[id];
// ~~~~~~~~~~
// strict では error TS2322: Type 'string | undefined' is not assignable to type 'string'.
//
// 訳: 型 'string | undefined' を型 'string' に割り当てることはできません。
}
戻り値の型を書かなければ、この関数の型は string | undefined と推論され、問題は呼び出し側で表面化します。原因から離れた場所でエラーになります。
function log(msg: string): void {
console.log(msg);
}
| 型 | 意味 |
|---|---|
void | 戻り値を使わない。何を返してもよいが、呼び出し側は使ってはいけない |
undefined | undefined を返す。return; または return undefined; が必要 |
never | 戻ってこない。必ず例外を投げるか、無限ループする |
function fail(message: string): never {
throw new Error(message);
}
never は 08. 型の絞り込み の網羅性チェックでも使います。
// ① 省略可能(undefined になりうる)
function greet1(name: string, title?: string) {
return title ? `${title} ${name}` : name;
}
// ② デフォルト値(省略すると既定値が入る)
function greet2(name: string, title = "様") {
return `${title} ${name}`;
}
// ③ 可変長引数
function join(sep: string, ...parts: string[]) {
return parts.join(sep);
}
省略可能引数は末尾にしか置けません。
function f(a?: string, b: number) はエラーです。JavaScript の引数は位置で決まるため、途中を飛ばして渡せないためです。引数が増えてきたらオブジェクトで受け取る形に変えてください。
function createUser(opts: { name: string; age?: number; email?: string }) { /* ... */ }
createUser({ name: "Alice", email: "a@example.com" }); // 順序に縛られない
function f(title = "様") {
// title の型は string(string | undefined ではない)
return title.length;
}
f(); // OK
f(undefined); // OK — undefined を渡すと既定値が使われる
// args.ts
function greet(name: string, title = "様"): string {
return `${title} ${name}`;
}
function join(sep: string, ...parts: string[]): string {
return parts.join(sep);
}
console.log(greet("田中"));
console.log(greet("Alice", "Ms."));
console.log(join(" / ", "a", "b", "c"));
$ npx tsx args.ts
様 田中
Ms. Alice
a / b / c
TypeScript のオーバーロードは「シグネチャを複数書き、実装は1つ」という形です。
// シグネチャ(呼び出し側から見える形)
function parse(input: string): string[];
function parse(input: number): number[];
// 実装(呼び出し側からは見えない)
function parse(input: string | number): string[] | number[] {
if (typeof input === "string") {
return input.split(",");
}
return [input];
}
const a = parse("x,y"); // string[]
const b = parse(5); // number[]
Java・C# から来た人へ
Java・C# のオーバーロードは別々のメソッドが複数存在し、コンパイラが呼び分けます。TypeScript では関数は1つだけで、型の上で複数の見え方を提供しているにすぎません。
実装側で自分で分岐を書く必要があります。シグネチャと実装が食い違っていても、実装側の型が緩ければエラーになりません。書くときは注意してください。
オーバーロードは引数と戻り値の組み合わせが連動するときにだけ必要です。
// オーバーロードは不要 — union で書ける
function double(x: number | string): string {
return typeof x === "number" ? String(x * 2) : x + x;
}
| 状況 | 書き方 |
|---|---|
| 戻り値の型が引数によって変わる | オーバーロード、またはジェネリクス(09) |
| 戻り値の型は同じ | union で受ける |
| 引数の数が違うだけ | 省略可能引数・デフォルト値 |
05. 構造的型付けで触れたとおり、要求より引数が少ない関数は渡せます。
const nums = [1, 2, 3];
nums.map((x) => x * 2); // OK
nums.map((x, i) => x * i); // OK — 2つ目も受け取れる
nums.map((x, i, arr) => arr.length); // OK — 3つ目まである
これは JavaScript の性質に合わせた仕様です。
JavaScript では、渡された引数を無視できます。map は常に3つの引数を渡しますが、受け取る側は必要な数だけ書けばよいという書き方が普通です。逆に多すぎるとエラーになります(受け取れない値を要求しているため)。
type Handler = (x: number) => void;
const h: Handler = (x) => x * 2; // OK。数値を返しているが通る
void は「戻り値を使わない」という意味なので、何を返しても構いません。ただし呼び出し側は戻り値を使えません。
この性質が事故になることがあります。
const ids: string[] = [];
const users = [{ id: "1" }, { id: "2" }];
// forEach のコールバックは void を返す想定
users.forEach((u) => ids.push(u.id)); // push は number を返すが、無視される(OK)
これは問題ありません。しかしasync なコールバックを forEach に渡すと、待たれずに進みます。この扱いは 11. 非同期とエラー処理 で扱います。
// callback.ts
type Mapper<T, U> = (item: T, index: number) => U;
function mapAll<T, U>(items: T[], fn: Mapper<T, U>): U[] {
const out: U[] = [];
for (let i = 0; i < items.length; i++) {
out.push(fn(items[i], i));
}
return out;
}
console.log(mapAll([1, 2, 3], (n) => n * 10));
console.log(mapAll(["a", "b"], (s, i) => `${i}:${s}`));
$ npx tsx callback.ts
[ 10, 20, 30 ]
[ '0:a', '1:b' ]
<T, U> の記法はジェネリクスです。09. ジェネリクス で扱います。
コールバック内の this が問題になる場合、第1引数として this の型を宣言できます。
function handler(this: HTMLElement, event: Event) {
console.log(this.tagName); // this の型が分かる
}
この this は引数ではありません。型検査のためだけの宣言で、呼び出し側は渡しません。アロー関数を使えば this の問題自体が起きないため、必要になる場面は限られます。
まとめ
| 項目 | この章の結論 |
|---|---|
| 戻り値の型 | 公開する関数には書く。実装ミスをその関数の中で止められる |
| 省略可能引数 | 末尾にしか置けない。増えたらオブジェクトで受け取る |
| オーバーロード | 実装は1つだけ。Java・C# とは別の仕組み。多くは union で足りる |
| コールバック | 引数は少ない分には通る。void は「戻り値を使わない」の意味 |
次は 08. 型の絞り込み です。この資料の山場のもうひとつで、TypeScript を「書ける」から「使える」に変える章です。