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の要素型はnumberfilterのコールバックscore => score >= 60は、score: numberを受け取ってbooleanを返している- 「残るのも
numberだけ」
と判断して、passed を number[] と推論します。
コールバックの引数 score に型を書いていなくても、
「元の配列が number[] だから、ここは number だな」と自動で推論されます。
scores.filter(score => {
// score: number と推論されている
return score % 2 === 0;
});
TypeScriptfilter のコールバックは「その要素を残すかどうか」を 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
TypeScriptTypeScript から見ると、「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";
}
TypeScriptv 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 | stringfilterに渡した関数isNumberの型は(v: number | string) => v is number- 「true が返った要素は number だ」と TypeScript が理解できる
その結果、onlyNumbers は number[] と推論されます。
ポイントは、「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[]
TypeScriptv is T という型述語のおかげで、
「この filter を通ったものは T(ここでは string)だ」と TypeScript が理解できるようになります。
filter のコールバック引数の型推論
引数の型は「元の配列の要素型」から自動で決まる
const nums = [1, 2, 3, 4]; // number[]
nums.filter(n => {
// n: number と推論されている
return n % 2 === 0;
});
TypeScriptmap と同じく、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 の型チェックがない)
TypeScriptany は「型チェックを放棄する」ので、filter の中でも「x にどんなプロパティがあるか」「どんな演算が妥当か」を一切見てくれません。
filter の型推論をちゃんと効かせたいなら、
元の配列を any[] にしないことが本当に大事です。
unknown[] は「ちゃんと絞り込めば安全」
const xs: unknown[] = [1, "2", null];
const numbers = xs.filter((x): x is number => typeof x === "number");
// numbers: number[]
TypeScriptunknown は「何か分からない」なので、そのままでは何もできませんが、
型述語を使って「number だけ残す」と宣言すれば、filter の結果は number[] として扱えます。
unknown[] は any[] よりずっと安全で、
「ちゃんと絞り込んでから使う」というスタイルと相性がいいです。
初心者がまず掴んでおきたい「filter の型推論」の感覚
filter の型推論の本質は、次の2段階です。
- 「元の配列の要素型」から、コールバックの引数の型が決まる
- 「コールバックの戻り値の型」から、「何が残る配列か」を推論する
ここで、戻り値の型がただの boolean だと、
TypeScript は「値は絞り込まれているかもしれないけど、型としてはまだ混ざっているかも」と考えがちです。
「この filter で“この型だけが残る”と分かってほしい」ときは、
v is 型という「型述語」を使った関数を用意して- それを
filterに渡す
という書き方を覚えておくと、一気に世界が変わります。
「値の世界では“条件で絞り込んでいる”」
「型の世界では“型述語で絞り込んでいる”」
この二重構造を意識できるようになると、filter はただの「要素を減らすメソッド」から、
「配列の型を洗練させていく道具」に変わっていきます。
