Array.prototype.filter の型推論は、条件式が複雑な場合があるため、配列の要素を絞り込むことができません。
const n = [0, null].filter(item => item !== null)
// const n: (number | null)[]
console.log(n) // [0]
// number[] であって欲しい。。。
この程度の単純なプリミティブのフィルタリングならば、出来そうな予感がしたので試してみました。Array.prototype.filter の推論ではありませんが、Assertion 付き関数でそれっぽくなりました。実処理は typeof 演算子で比較しているので、プリミティブ型が一致する場合のフィルタリングです。
type Primitive = boolean | number | string | symbol | bigint | null | undefined
type Unwrap<T> = T extends { [K in keyof T]: infer U } ? U : never
function filter<T extends Primitive[], U extends Primitive>(array: T, diff: U) {
return array.filter(item => typeof item !== typeof diff) as Exclude<Unwrap<T>, U>[]
}
const n1 = filter([0, null], null)
// const n1: number[]
console.log(n1) // [0]
const n2 = filter([0, null], 0)
// const n2: null[]
console.log(n2) // [null]
const n3 = filter([0, false, null], 0)
// const n3: (boolean | null)[]
console.log(n3) // [false, null]
const n4 = filter([0, false, null], 1) // number型が絞られる
// const n4: (boolean | null)[]
console.log(n4) // [false, null]
const n5 = filter(filter(filter([0, false, null, undefined], undefined), null), false)
// const n5: number[]
console.log(n5) // [0]
TypeScript 3.4 の場合
const assertion を利用することで、Tuple 宣言が楽です。
今度は typeof の比較でなくても良いため、実装と推論が近づきます。
function filter<T extends readonly Primitive[], U extends Primitive>(array: T, diff: U) {
return array.filter(item => item !== diff) as Exclude<Unwrap<T>, U>[]
}
const n1 = filter([0, 1, 2] as const, 0)
// const n1: (1 | 2)[]
console.log(n1) // [1, 2]
const n2 = filter([0, 1, 2] as const, 1)
// const n1: (0 | 2)[]
console.log(n2) // [0, 2]
const n3 = filter([0, 1, 2] as const, 2)
// const n1: (0 | 1)[]
console.log(n3) // [0, 1]