JavaScript で配列から Falsy な値をフィルターする際に Array.filter(Boolean)
をよく使っていたのですが TypeScript だと型がうまく推論されなかったので TypeScript で配列から Falsy な値を取り除く方法を調べてたメモ
結論
思った以上に沼だった…
TypeScript には is
はあるが、〇〇以外 NOR を表すものがない
TypeScript には現状 (v4.9.3
現在) NaN
を表す型が無いので .filter(Boolean)
の結果を型情報にすることはできなかった(NaN
はあくまで number 型)
暗黙的な Falsy は 0
が削除されてしまったり逆にバグの温床になるので、Flasy を除去しなけれなならない状況なのは設計を見直したほうが良いのではないか?
NonNullable
で null
と undefined
を除去する程度に使うのが良さそう
null
と undefined
を除去する Filter
const nonNullableFilter =
< T>( value: T) : value is NonNullable< T> => ( value !== null && value !== undefined );
array.filter( nonNullableFilter);
false
… false キーワード
0
... 数値ゼロ
-0
... 数値マイナスゼロ
0n
… BigInt で、論理値として使用された場合、 Number と同じ規則に従います。 0n は偽値です。
""
… 空文字列の値
null
… null
- 何も値が存在しないこと
undefined
… undefined
- プリミティブ値
NaN
… NaN
- 非数
cf. Falsy (偽値) - MDN Web Docs 用語集: ウェブ関連用語の定義 | MDN
Falsy な値は Boolean()
で false
になるので、.filter(Boolean)
で除外できる
[ 0 , -0 , 0n, false , null , undefined , NaN , "" ] .map(( item) => Boolean ( item))
[ 0 , -0 , 0n, false , null , undefined , NaN , "" ] .filter( Boolean )
TypeScript で Array.filter(Boolean)
の型推論 がうまく行かない
const arr = [ 0 , -0 , 0n, false , null , undefined , NaN , "" ] ;
const newArr = arr.filter( Boolean );
.filter(Boolean)
をしても元の配列の型のまま推論されてしまう
1. 型情報から null
と undefined
を除く
Falsy な値は型が複雑になりそうなので、まずは nullable な型情報を除去してみる。NonNullable
を使うと nullable (null
と undefined
) を除去できる
const arr = [ 0 , -0 , 0n, false , null , undefined , NaN , "" ] ;
const newArr = arr.filter(( item) : item is NonNullable<typeof item> =>
( item !== null && item !== undefined ));
コールバック関数をユーティリティ化する
毎回 (item): item is NonNullable<typeof item> => (item !== null && item !== undefined)
を書くのは大変なので、ユーティリティ化しておくと便利
const nonNullableFilter = < T>( value: T) : value is NonNullable< T> => ( value !== null && value !== undefined );
👇 ユーティリティに置換
const arr = [ 0 , -0 , 0n, false , null , undefined , NaN , "" ] ;
const newArr = arr.filter( nonNullableFilter);
📝 null 合体演算子 (??
) を使う方法
Null 合体演算子 (??
) は論理演算子 の一種です。この演算子 は左辺が null
または undefined
の場合に右の値を返し、それ以外の場合に左の値を返します。
cf. Null 合体演算子 (??) - JavaScript | MDN
const arr = [ 0 , -0 , 0n, false , null , undefined , NaN , "" ] ;
arr.filter(( item) : item is NonNullable<typeof item> =>
( item ?? false ));
const newArr = arr.filter(( item) : item is NonNullable<typeof item> =>
!! ( item ?? false ));
ただ値によっては false ?? false
になっていたりで少し気持ち悪い。個人的に明示的に (item !== null && item !== undefined)
としておいた方が見通しが良いのでは?という気もする
2. 型情報からも Falsy な値を除去したい
⚠ NaN は上手く除去できない
NaN
は number 型でしかなく型として定義できない
type Falsy = 0 | -0 | 0n | false | null | undefined | '' | NaN ;
=> 'NaN' refers to a value, but is being used as a type here. Did you mean 'typeof NaN'?
NaN
をプリミティブ型として定義することはできない
NaN == NaN
が false
なのでプリミティブ型にできても、そもそも判定できない
NaN
かどうかは isNaN(number)
でしか判定できない
型エラーにならないようにtypeof NaN
とすると、typeof NaN = number
なので number型 が Falsy に含まれてしまう事になるので良くない
2-1. .filter(Boolean)
を型定義のある関数に置き換える
.filter(Boolean)
は .filter((item) => Boolean(item))
の省略形
コールバック関数を型定義のあるものに置き換える
type Falsy = 0 | -0 | 0n | false | null | undefined | '' ;
const isTruthy = < T>( x: T | Falsy) : x is T => !! x;
const isTruthy = <T>(x: T | Falsy): x is T => !!x;
の解説
引数 x
の型が T
または Falsy
!!x
が true
なら x is T
(Falsy
ではない)
2-2. 渡す配列を as const
しなければ正しく判定できない
type Falsy = 0 | -0 | 0n | false | null | undefined | '' ;
const isTruthy = < T>( x: T | Falsy) : x is T => !! x;
const result = [ 0 , -0 , 0n, false , null , undefined , '' , 1 , '2' ] .filter( isTruthy);
const result = ( [ 0 , -0 , 0n, false , null , undefined , '' , 1 , '2' ] as const ) .filter( isTruthy);
渡す配列を as const
してないと isTruthy
の x: T | Falsy
の部分で number や bigint や boolean という型情報として判定されるので T
扱いになり、0
や 0n
や false
が除去されているか判別されない。
as const
する事でプリミティブな値として渡されるので 0
や 0n
や false
という値として渡されるので Falsy型 の値だと判定されるという仕組み。
NaN
があると as const
しても number 型が帰る
type Falsy = 0 | -0 | 0n | false | null | undefined | '' ;
const isTruthy = < T>( x: T | Falsy) : x is T => !! x;
const result = ( [ 0 , -0 , 0n, false , null , undefined , '' , 1 , '2' , NaN ] as const ) .filter( isTruthy);
const result = ( [ 0 , -0 , 0n, false , null , undefined , '' , 1 , '2' , NaN ] as const ) .filter( isTruthy);
const result = ( [ NaN ] as const ) .filter( isTruthy);
所管
Array.filyer(Boolean)
で Falsy な値を除去した時に型情報も合わせたかっただけなのですが思った以上に沼だった。
TypeScript には is
はあるが、〇〇以外 NOR を表すものがない
NaN は number 型 で NaN === NaN
が false
なので型でキャッチできない
結果的に今の実力では NaN
も除去した型を作ることができなかった。現実的には TypeScript で暗黙的な型変換の Falsy で何かを判定することほぼ無いと思うので NonNullable
で null
と undefined
を取り除けく方法で十分な気がしている。(むしろ暗黙的な Falsy で判定すると意図的な 0
が除去されてしまうとかバグの原因だと思っている)
おわり
[参考]