NonNullable と is 演算子を組み合わせる


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 は例のように、nullundefined を取り除いた型を返すジェネリック型です。

確認: null や undefined を含む型の値を代入するとどうなるか

その前に、nullundefined を含む型の値を代入するとどうなるかを確認しておきます。

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 でコンパイルすると引用のようになります。undefinednull だからといって特別なことはなく、型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 の引数の dataData | 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 であれば引数の dataData 型と明示します。この isDatafilter の条件関数として渡すことで、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 はどんな型でも undefinednull を取り除くので、オプショナルな要素のフィルタリングが行えます。

ワンライナーで書く

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 の相談をする!


投稿者 Chaki Hironori

webライターもやってるフロントエンドエンジニアです。Reactは自信があります。またデザイン畑の出身で、気持ちのいいアニメーションやインタラクティブな表現は丁寧に手掛けます。好きなものは中南米の遺跡で、スペイン語が少しできます。