TypeScript | 基礎文法:配列・タプル – filterの型推論

TypeScript TypeScript
スポンサーリンク

filterの型推論は「何が残る配列か」を推理してくれる仕組み

filter は、「条件を満たす要素だけを残して新しい配列を作る」メソッドです。
TypeScript はここで、「元の配列の要素の型」と「filter に渡した関数の戻り値の型」から、「残った配列の型」を推論しようとします。

const nums = [1, 2, 3, 4]; // number[]

const evens = nums.filter(n => n % 2 === 0);
// evens: number[]
TypeScript

この場合はシンプルで、
元の配列が number[]、条件関数も「number を受け取って boolean を返す」だけなので、
「残るのも number だよね」と素直に number[] と推論されます。

ここまでは直感通りですが、union型や null/undefined を含む配列になると、一気に“型推論の面白さ”が出てきます。


基本形:単純な配列の filter の型推論

number[] に対する filter

const scores = [80, 90, 40, 100]; // number[]

const passed = scores.filter(score => score >= 60);
// passed: number[]
TypeScript

ここで TypeScript は、

  • scores の要素型は number
  • filter のコールバック score => score >= 60 は、score: number を受け取って boolean を返している
  • 「残るのも number だけ」

と判断して、passednumber[] と推論します。

コールバックの引数 score に型を書いていなくても、
「元の配列が number[] だから、ここは number だな」と自動で推論されます。

scores.filter(score => {
  // score: number と推論されている
  return score % 2 === 0;
});
TypeScript

filter のコールバックは「その要素を残すかどうか」を boolean で返すだけなので、
単純な配列の場合は「元の要素型がそのまま残る」と考えてOKです。


union型配列に対する filter の型推論(まずは素直なパターン)

(number | string)[] を「値の条件」で絞る

const mixed: (number | string)[] = [1, "2", 3, "4"];

const onlyNumbers = mixed.filter(v => typeof v === "number");
// onlyNumbers: (string | number)[]
TypeScript

ここで「え?」となるかもしれません。
人間からすると「typeof v === "number" なんだから、残るのは number だけでしょ」と思いますが、
この書き方だと TypeScript は 「v の型が絞り込まれたことを filter の外側まで覚えてくれない」 ことが多いです。

なぜかというと、filter のコールバックの戻り値の型はただの boolean だからです。

(v: number | string) => boolean
TypeScript

TypeScript から見ると、「true のときに v が number だ」とまでは分からない。
「条件は満たしてるけど、型としてはまだ (number | string) のままかもしれない」と考えてしまうわけです。


「型を絞り込む filter」を正しく書くためのキー:型述語(type predicate)

v is number という「型を教える戻り値の型」

TypeScript には、「この関数が true を返したとき、この引数はこの型だよ」とコンパイラに教えるための特別な戻り値の型があります。
それが「型述語(type predicate)」です。

function isNumber(v: unknown): v is number {
  return typeof v === "number";
}
TypeScript

v is number という戻り値の型は、
「この関数が true を返したなら、v は number だとみなしていい」という意味になります。

これを filter と組み合わせると、一気に型推論が賢くなります。

型述語を使った filter

const mixed: (number | string)[] = [1, "2", 3, "4"];

function isNumber(v: number | string): v is number {
  return typeof v === "number";
}

const onlyNumbers = mixed.filter(isNumber);
// onlyNumbers: number[]
TypeScript

ここでは、

  • mixed の要素型は number | string
  • filter に渡した関数 isNumber の型は (v: number | string) => v is number
  • 「true が返った要素は number だ」と TypeScript が理解できる

その結果、onlyNumbersnumber[] と推論されます。

ポイントは、「filter に渡す関数の戻り値の型を、ただの boolean ではなく “〜 is 型” にすること」です。
これによって、「この filter は配列から特定の型を取り除いているんだよ」と TypeScript に教えられます。


null / undefined を取り除く filter と型推論

よくあるパターン:null や undefined を消したい

const values: (string | null | undefined)[] = [
  "alpha",
  null,
  "beta",
  undefined,
  "gamma"
];

const cleaned = values.filter(v => v !== null && v !== undefined);
// cleaned: (string | null | undefined)[]
TypeScript

人間からすると「null と undefined を除いたんだから、残るのは string だけでしょ」と思いますが、
TypeScript は「本当にそうかどうかまでは分からない」と判断して、型を絞り込んでくれません。

ここでも、型述語を使うと一気にスッキリします。

function isNotNullOrUndefined<T>(v: T | null | undefined): v is T {
  return v !== null && v !== undefined;
}

const cleaned2 = values.filter(isNotNullOrUndefined);
// cleaned2: string[]
TypeScript

v is T という型述語のおかげで、
「この filter を通ったものは T(ここでは string)だ」と TypeScript が理解できるようになります。


filter のコールバック引数の型推論

引数の型は「元の配列の要素型」から自動で決まる

const nums = [1, 2, 3, 4]; // number[]

nums.filter(n => {
  // n: number と推論されている
  return n % 2 === 0;
});
TypeScript

map と同じく、filter のコールバック引数の型も、
「元の配列の要素型」から自動で推論されます。

インデックスや配列全体も同様です。

nums.filter((value, index, array) => {
  // value: number
  // index: number
  // array: number[]
  return index % 2 === 0;
});
TypeScript

ここにわざわざ型を書く必要はほとんどありません。
むしろ、書かない方が TypeScript の推論とズレにくくて安全なことが多いです。


filter と any[] / unknown[] の違い

any[] だと「何も守ってくれない」

const xs: any[] = [1, "2", null];

const result = xs.filter(x => x > 0);
// これも通ってしまう(x の型チェックがない)
TypeScript

any は「型チェックを放棄する」ので、
filter の中でも「x にどんなプロパティがあるか」「どんな演算が妥当か」を一切見てくれません。

filter の型推論をちゃんと効かせたいなら、
元の配列を any[] にしないことが本当に大事です。

unknown[] は「ちゃんと絞り込めば安全」

const xs: unknown[] = [1, "2", null];

const numbers = xs.filter((x): x is number => typeof x === "number");
// numbers: number[]
TypeScript

unknown は「何か分からない」なので、そのままでは何もできませんが、
型述語を使って「number だけ残す」と宣言すれば、
filter の結果は number[] として扱えます。

unknown[]any[] よりずっと安全で、
「ちゃんと絞り込んでから使う」というスタイルと相性がいいです。


初心者がまず掴んでおきたい「filter の型推論」の感覚

filter の型推論の本質は、次の2段階です。

  1. 「元の配列の要素型」から、コールバックの引数の型が決まる
  2. 「コールバックの戻り値の型」から、「何が残る配列か」を推論する

ここで、戻り値の型がただの boolean だと、
TypeScript は「値は絞り込まれているかもしれないけど、型としてはまだ混ざっているかも」と考えがちです。

「この filter で“この型だけが残る”と分かってほしい」ときは、

  • v is 型 という「型述語」を使った関数を用意して
  • それを filter に渡す

という書き方を覚えておくと、一気に世界が変わります。

「値の世界では“条件で絞り込んでいる”」
「型の世界では“型述語で絞り込んでいる”」

この二重構造を意識できるようになると、
filter はただの「要素を減らすメソッド」から、
「配列の型を洗練させていく道具」に変わっていきます。

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