TypeScriptで自作バリデーション関数を型安全に設計する方法!初心者向けデータバリデーション完全解説
生徒
「TypeScriptで、入力されたデータが正しいかどうかをチェックして、さらに安全にプログラムで使えるようにする方法はありますか?」
先生
「TypeScriptでは、自作バリデーション関数という仕組みを作ることで、データのチェックと型の安全性を同時に保証することができます。」
生徒
「自作バリデーション関数を型安全に設計するには、具体的にどうすればいいのですか?」
先生
「それでは、未経験の方でも仕組みがしっかりと理解できるように、基礎から順番に学んでいきましょう!」
1. データバリデーションとは?
データバリデーションとは、プログラムに入力されるデータが、あらかじめ決められたルールに合っているかどうかを確認する作業のことです。日本語では「データ妥当性確認」や「入力チェック」などと呼ばれることもあります。
たとえば、スマートフォンのアプリで新しく会員登録をするときを想像してみてください。画面の入力欄に「年齢」を入れる場所があるとします。ここに数字ではなく「こんにちは」という文字が入力されたり、マイナスの数字が入力されたりしたら困ってしまいます。このようなおかしなデータがプログラムの奥深くに入り込まないように、入り口でしっかりと検査をして、正しいデータだけを通過させる仕組みがデータバリデーションです。
インターネット上のWebサイトやアプリケーションでは、ユーザーがどのような操作をするか予測できません。意図しない文字が入力されたり、何も入力されずにボタンが押されたりすることが日常茶飯事です。そのため、開発者が責任を持ってデータをチェックする防波堤を作らなければなりません。この防波堤の役割を果たすのが、バリデーションという処理になります。
2. TypeScriptでデータをチェックする重要性
TypeScriptというプログラミング言語は、データに「型」という名札をつけることで、プログラムの間違いを事前に防いでくれる非常に便利な言語です。しかし、そんな優秀なTypeScriptにも苦手な場面があります。それが、プログラムの外部からやってくるデータの扱いです。
たとえば、インターネットを通じて別のサーバーから受け取るデータや、画面の入力フォームからユーザーが手入力したデータは、プログラムを実行するその瞬間まで、本当に正しい型であるかどうかがわかりません。TypeScriptはプログラムを書いている最中に間違いを指摘してくれますが、実行中に突然やってくる未知のデータに対しては、そのままでは無力になってしまいます。
もし、数値が入っていると信じ込んでいた場所に文字が入ってきた場合、プログラムは途中でエラーを起こして停止してしまうか、おかしな計算結果を出力してしまいます。これを防ぐために、外部から来た正体不明のデータを安全なデータへと変換するためのチェック処理が、TypeScriptにおいて極めて重要になってきます。
3. 型安全とはどういう意味?
プログラミングの学習を進めると「型安全」という言葉をよく耳にするようになります。型安全とは、一言で言えば「データの種類に関する勘違いによるバグを、仕組みとして絶対に発生させない状態」のことを指します。
パソコンの内部では、あらゆるデータが処理されていますが、人間にとっては「文字」も「数値」も「日付」もすべて異なる性質を持ったものです。文字に対して引き算をすることはできませんし、数値に対して文字を付け足す処理は特別なルールが必要です。型安全が守られているプログラムでは、文字が入るべき場所に数値が入ろうとした段階で、パソコンが「そこはデータを入れる場所が違います」と警告を出してくれます。
自作バリデーション関数を型安全に設計するということは、単に「データが正しいか」を画面上でチェックするだけでなく、そのチェックを通過したデータが、以降のプログラムの中で「確実にその型である」とTypeScriptが100パーセント信頼して処理できるようにルール付けをすることを意味します。これにより、開発者は安心して次のプログラムを書くことができるようになります。
4. 自作バリデーション関数の基本の作り方
それでは、実際にTypeScriptを使って、独自のチェックを行う「自作バリデーション関数」を作ってみましょう。まずは一番シンプルな例として、入力されたデータが本当に「文字列」であるかどうかを判定する関数を考えてみます。
関数とは、特定の処理を一つにまとめた部品のようなものです。データを引き渡すと、そのデータを調べて、結果を返してくれます。今回は、渡されたデータの種類が文字列であれば「正しい」という意味のデータを返し、そうでなければ「間違い」という意味のデータを返す関数を記述します。
function checkIsString(input: any): boolean {
return typeof input === "string";
}
上記のコードについて細かく解説します。まずfunctionは関数を作るための宣言です。その横にあるcheckIsStringが関数の名前になります。括弧の中にあるinput: anyは、この関数があらゆる種類のデータを受け付けるという意味です。anyは何でも許可する特別な型です。そして、括弧の右側にある: booleanは、この関数が最終的に「はい」か「いいえ」の二択のデータだけを返すという約束です。プログラミングでは「はい」をtrue、「いいえ」をfalseと呼びます。
関数の中身にあるtypeof input === "string"は、受け取ったデータの種類が文字列であるかを調べています。文字列であればtrueになり、数値やその他のデータであればfalseになります。実行結果を確認してみましょう。
checkIsString("こんにちは"); // 結果は true
checkIsString(123); // 結果は false
5. データの型を確定させる魔法の仕組み「型ガード」とは
先ほどの関数でデータのチェックはできるようになりましたが、これだけではまだ「型安全」とは言えません。なぜなら、チェックを通過した後も、TypeScriptは該当のデータが何であるかを明確に確信できていないからです。
そこで登場するのが「型ガード」という概念です。型ガードとは、特定の条件を満たしたときに、データの型をより狭い範囲に限定して確定させる仕組みのことです。たとえば、中身がわからない箱があるとします。外側からセンサーで調べて「これは果物である」と分かったら、それ以降はその箱を果物として安全に扱えるようになります。このように、あやふやだったデータの正体をはっきりとさせる防衛策が型ガードです。
TypeScriptには、この型ガードを自作の関数で実現するための特別な書き方が用意されています。その書き方をマスターすることで、チェック機能と型確定の機能を同時に持った、強力なバリデーション関数を設計することができるようになります。
6. is演算子を使って型安全にバリデーション関数を作る方法
自作バリデーション関数を型安全の領域へと引き上げるために最も重要なのが、is演算子という特殊な記法です。これを使って、先ほどの関数を改良してみましょう。関数の戻り値の型を指定していた部分を書き換えます。
function isString(input: any): input is string {
return typeof input === "string";
}
注目すべきは、関数の括弧の右側にあるinput is stringという記述です。これがis演算子を使った記述です。これはTypeScriptに対して「この関数がtrueを返したならば、渡されたinputというデータの中身は絶対にstring(文字列)型であると認定してください」という強力なメッセージを伝える役割を持っています。
ただのbooleanを指定した場合は、関数がtrueを返しても、パソコンは「ただ条件式が成立しただけ」としか認識せず、データの種類までは変更してくれません。しかし、input is stringと書くことで、関数の外側のプログラムに対してもその安全性が波及するようになります。
7. 実際のプログラムで自作バリデーション関数を使ってみよう
それでは、作成した型安全な自作バリデーション関数を、実際の条件分岐のプログラムに組み込んで使ってみましょう。条件分岐を行うにはif文という仕組みを使います。
今回は、何が入っているか分からない変数を用意し、それを関数に投入して処理を分けてみます。以下のプログラムを見てください。
function isString(input: any): input is string {
return typeof input === "string";
}
let unknownData: any = "プログラミングの世界へようこそ";
if (isString(unknownData)) {
console.log(unknownData.toUpperCase());
} else {
console.log("これは文字列ではありません");
}
このプログラムでは、if (isString(unknownData))という部分でチェックを行っています。もし、このチェックを通過してif文の中身に進んだ場合、TypeScriptはunknownDataが文字列であると確信しています。そのため、文字列専用の機能であるtoUpperCaseという、アルファベットを大文字に変換する命令をエラーなしで呼び出すことができます。
もしis演算子を使っていなければ、パソコンは「文字列ではないかもしれないから、そんな命令は実行させられません」とエラーを出してしまいます。自作関数に正しい型保証を与えることで、このようにスムーズで安全なコードが実現します。実行結果は以下の通りです。
プログラミングの世界へようこそ
8. 不正なデータを防ぐための実践的な応用例
文字列のチェックだけでなく、実務ではもっと複雑なデータをチェックすることが求められます。たとえば、Webアプリのユーザーデータとして「名前(文字列)」と「年齢(数値)」が正しくセットになっているオブジェクトという仕組みをチェックする場合です。オブジェクトとは、複数のデータをひとまとめにしたファイルのようなものです。
最後に応用として、特定の形をしたデータ構造を完全にチェックするバリデーション関数を設計してみましょう。型定義を行い、それに基づいたチェックを実装します。
type User = {
name: string;
age: number;
};
function isUser(input: any): input is User {
return (
input !== null &&
typeof input === "object" &&
typeof input.name === "string" &&
typeof input.age === "number"
);
}
let rawData: any = { name: "太郎", age: 25 };
if (isUser(rawData)) {
console.log("ユーザー名は " + rawData.name + " です");
} else {
console.log("不正なデータ形式です");
}
この応用例では、最初にtype Userという記述で、理想的なユーザーデータの形を定義しています。そして、isUserという関数の中で、データが空っぽ(null)ではなく、オブジェクトの形をしており、さらに内部のデータがすべて条件を満たしているかを細かく検査しています。すべてをクリアして初めてUser型であると認定されます。これを出力してみます。
ユーザー名は 太郎 です
このように、データの構造がどれだけ複雑になっても、仕組みは同じです。入り口で細かくチェックを行い、is演算子を使ってTypeScriptにその結果を報告することで、プログラム全体の安全性を劇的に高めることができます。これが、TypeScriptにおけるデータバリデーション設計の神髄です。