NonNullable と i s 演算子を組み合わせる
Gaji-Labo フロントエンドエンジニアの茶木です。
Gaji-Labo は、スタートアップ支援としてプロダクトチームの一員となるスタイルで開発をしています。 そういった開発では TypeScript の使用が標準的です。その理由は TypeScript が検出する型定義の問題から発見できるエラーは多く、開発のクオリティに直結するためです。
今記事では、TypeScript のジェネリクスである NonNullable
と、is
演算子について述べます。
NonNullable とは?
参考: https://www.typescriptlang.org/docs/handbook/utility-types.html#nonnullabletype
type T0 = NonNullable<string | number | undefined>;
// type T0 = string | number
type T1 = NonNullable<string[] | null | undefined>;
// type T1 = string[]
コード引用元: https://www.typescriptlang.org/docs/handbook/utility-types.html#example-9
NonNullable
は例のように、null
と undefined
を取り除いた型を返すジェネリック型です。
確認: null や undefined を含む型の値を代入するとどうなるか
その前に、null
や undefined
を含む型の値を代入するとどうなるかを確認しておきます。
let x: number;
let y: number | undefined;
let z: number | null | undefined;
x = 1; // Ok
y = 1; // Ok
z = 1; // Ok
x = undefined; // Error
y = undefined; // Ok
z = undefined; // Ok
x = null; // Error
y = null; // Error
z = null; // Ok
x = y; // Error
x = z; // Error
y = x; // Ok
y = z; // Error
z = x; // Ok
z = y; // Ok
コード引用元: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-0.html#example
TypeScript では --strictNullChecks
でコンパイルすると引用のようになります。undefined
や null
だからといって特別なことはなく、型Aが型Bを構成する要素をすべて持っていれば代入でき、そうでなければエラーになるということです。
filter 使用時のよくある型エラー
NonNullable
はいったんおいておいて、よくある困った例です。
type ResultItem = {
id: string;
data: Data | undefined;
}
function methodSomething(data: Data) {
return data.xxx;
}
const results: ResultItem[] = getItems();
const validResults = results.filter((item) => item.data );
// methodSomething の引数は Data | undefined 型のままとみなされ、型エラーになる
validResults.forEach((item) => methodSomething(item.data));
Array.filter
で、undefined
を含む data
を除去したリストを作成しています。 つまり filter
の戻り値の validResults
は { id: string; data: Data }[]
と想定されますが、methodSomthing
の引数の data
は Data | undefined
を渡していると推論されエラーとなります。
is 演算子を使う
この問題は is
演算子と組み合わせることで、回避できます。
function isData(data: Data): data is Data {
return Boolean(data);
}
const results: ResultItem[] = getItems();
const validResults = results.filter(isData);
// methodSomething の引数は Data型とみなされ、型エラーにならない
validResults.forEach((item) => methodSomething(item.data));
is
演算子は 引数 is 型
のように指定し、戻り値が true
のとき引数の型を、指定した型で明示します。
この例では、isData
を抜けるとき、戻り値が true
であれば引数の data
を Data
型と明示します。この isData
を filter
の条件関数として渡すことで、filter
を通過した要素の集合は、すべて、{ id: string; data: Data }
となり、 methodSomething
のエラーを回避できます。
汎用化する
function isNonNullable<T>(data: T): data is NonNullable<T> {
return Boolean(data);
}
const nonNullableResults = results.filter(isNonNullable);
ここで NonNullable
と組み合わせて汎用化ができます。 NonNullable
はどんな型でも undefined
や null
を取り除くので、オプショナルな要素のフィルタリングが行えます。
ワンライナーで書く
const nonNullableResults = results.filter((i): i is NonNullable<typeof i> => Boolean(i));
あるいは、 アロー演算子と組み合わせてワンライナーでも書けます。
おわりに
TypeScript は便利ですが、想像と違う振る舞いをみせることがあります。こういった挙動を把握し、回避方法を知ることで、TypeScript はもっと便利に使っていけます。そして、リーダブルなコードに保つことで、チームで共有しやすく、変更に強いプロダクトを作っていけると考えています。
Gaji-Labo はさまざまな技術で良いプロダクトを作る仕事を通して、スタートアップのプロダクト支援を行っていきます。
Gaji-Labo は Next.js, React, TypeScript 開発の実績と知見があります
フロントエンド開発の専門家である私たちが御社の開発チームに入ることで、バックエンドも含めた全体の開発効率が上がります。
「既存のサイトを Next.js に移行したい」
「人手が足りず信頼できるエンジニアを探している」
「自分たちで手を付けてみたがいまいち上手くいかない」
フロントエンド開発に関わるお困りごとがあれば、まずは一度お気軽に Gaji-Labo にご相談ください。
オンラインでのヒアリングとフルリモートでのプロセス支援にも対応しています。
Next.js, React, TypeScript の相談をする!