JavaScriptで型を書いたことがありますか?
この記事は、型を利用して安全に開発ができるようになるための方法をご紹介します。
この記事で得られること
JavaScriptの開発で型を利用できるようになります。
型の支援を受けるようになるとこんなメリットがあります。
- インテリセンスの利用によるタイプミス防止
- ソースコードの可読性の向上
- 開発速度・精度の向上
特に開発速度・精度の向上はかなりの効果を発揮します。
またAIによるサジェストの精度向上にも寄与するようです。
対象とする読者
私はサイボウズ社のkintoneを利用した業務システム開発を生業にしています。
kintoneではカスタムJavaScriptをたくさん書くことになります。JavaScriptについて教えることが多いので、ここに書いて残すことにしました。
ですので、JavaScriptを書くようになった初心者の方を想定しています。
JavaScriptの基礎技術を教えるときに、最初に型システムを伝えます。
知らない人が多く、もっともコード品質の向上に効果がある技術だからです。
なので、初心者じゃないけど初めて知ったという方も、ご一読いただければ楽しめると思います。
それでははじめましょう。
JSDocについて
JavaScriptにはJSDocという規格があります。これは、JavaDocに似ています。
ただ、Javaは静的型付言語ですので、型を宣言するときにJavaDocは必要ありません。
一方でJavaScriptは動的なので、本来は型を宣言することはできないのですが、VSCodeのようなJavaScript開発に利用される開発環境では、JSDocを書くことによって、型を推察させることができます。
型チェックの適用
TypeScriptはJavaScriptに型を導入したものですが、この技術を逆輸入して、JavaScriptでも記述の整合を確認できます。学び初めから型を正しく定義することは難しいですが、チェックを受けることは可能なのです。
//@ts-check
を利用します。
// @ts-check
let a = 2;
a = false;
上記のコードはVSCodeでは、"number"変数への"boolean"の代入になりますので、VSCodeのTS/JSチェック機能でエラー検出ができます。
このようにして、厳密な型を定義することにより、 予期せぬ不具合を防ぐことができます 。
プロパティの推察
以下のようなコードを考えてみましょう。
let a = {
name: "John",
age: 30,
city: "New York"
}
console.log(a.city.address);
JavaScriptが読める方はこのコードがエラーになるのが分かると思います。
a.city = "New York"ですので、addressというプロパティがないからです。
しかし、 複雑なJavaScriptを書いていると、こういったことはよくあります よね。
するとこのように、 実際に動かすことなく未然に エラーを防ぐことができます。
変数の明示的な型宣言
VSCodeで以下のようなJavaScriptコードを書いてみます。
let a = {
type :"number", value : 1
}
a = {
type : 'string', value : 'hello'
}
console.log(a);
このコードは、aという変数が持つ役割を表していますね。
すなわち、aという変数は、
type="number"なら、valueはnumber型、
type="string"なら、valueはstring型
です。
しかし、ts-checkを入れるとエラーになるのが分かります。
これは、最初に代入されたデータ型がその変数の型を定義するからです。
つまり、 { type : string, value : number }
は想定されていますが、
{ type : "string", value : string }
はaの型として、想定されていない。
プログラマが意図しないエラー検出です。
コードが正しいのに、エディタ側の型の推察結果が間違っています。
ここへ 「明示的に型を宣言」 することによって問題を解決してみましょう。
//@ts-check
/**
* @type {{
* type : 'number',
* value : number
* } | {
* type : 'string',
* value : string
* }}
*/
let a = {
type :"number", value : 1
}
a = {
type : 'string', value : 'hello'
}
console.log(a);
すばらしい!
問題を回避できました。
@type
は型を宣言するためのアノテーション記述です。
/** @type {string | number} */
let variable
👆のとき変数variableはstring型、またはnumber型が代入可能な変数です。
/** @type {"aaa" | "bbb"} */
let variable
👆のとき変数variableは"aaa"という固定値か、"bbb"という固定値しかとることができません。
「/**」から始まるコメント内で、@type
を利用でき、
@type {型}
という記法で宣言ができます。
型
の内容を工夫することで、その変数が取りうる構造の範囲を決定づけます。
関数の型
変数の宣言法について知りました。関数も同じように宣言することが可能です。
//@ts-check
/** @type { (args : string) => number | string } */
function foo(args){
return 1;
}
const value = foo('hello');
foo関数は文字列(string)を受け取って、numberか、あるいはstringを返す関数です。
(引数名 : 型) => 戻り値
という記法で、関数を記述できます。
型の命名
文字列にはstring, 日付にはDateというように、JavaScriptにはデータ型が存在します。
ただ、先ほどの変数の明示的な型宣言にあるように、ユーザー定義型にも名前をつけたくなるときがあります。
//@ts-check
/**
* @type {{
* type : 'number',
* value : number
* } | {
* type : 'string',
* value : string
* }}
*/
let a;
/**
* @type {() => ({
* type : 'number',
* value : number
* } | {
* type : 'string',
* value : string
* })}
*/
function returnA() {
return {
type: 'number',
value: 1
}
}
このコードはaが持っている型を関数の定義でも利用しています。
すごく冗長ですよね。
これは書き直すことができます。
//@ts-check
/**
* @typedef {{
* type : 'number';
* value : number;
* } | {
* type : 'string';
* value : string;
* }} AType
*/
/**@type {AType} */
let a;
/**
* @type {() => AType}
*/
function returnA() {
return {
type: 'number',
value: 1
}
}
ATypeという名前の型を@typedefを利用して宣言しています。
@typedef {型の構造} 型名
という記法で宣言ができます。
TypeScriptファイルの利用
複雑な型を毎度宣言するのは面倒ですし、@typedef
もコメント内での複雑な型宣言に適してはいません。
そこで、型宣言にのみ、TypeScriptを導入してしまいましょう。
「それなら、いっそのことTSを全体導入すればいいじゃん」という方もいると思います。わたしも大賛成です。
なのですが、開発が進んでぐちゃっとしたJavaScriptを、TypeScriptに変えるのは骨が折れる作業です。(そういうJSは、型を開発に利用していないので、だいたいバグを埋め込んでしまっている)
なので、部分的にTypeScriptを導入します。ここでは、型宣言のみで利用します。
実際にやってみましょう。
ディレクトリを以下のように設置します。
- /root
- index.js : 開発対象のJSファイル
- types.d.ts : TypeScriptの型宣言ファイル
index.jsの開発に型宣言ファイルを導入すると以下のようになります。
types.d.tsで宣言したTypeAという型を、index.jsの開発側から読み込んでいます。
//types.d.ts
type TypeA = {
type: "a"
name: "hoge" | "bar";
value: string;
}
これは記述が面倒なように見えますが、めちゃくちゃ簡単に呼び出せます。
ほぼVSCodeのコードインテリセンスのみで記述ができます。
まとめ
ここまで、JavaScriptで型を用いた開発手法についてご紹介しました。
-
//@ts-check
によるTS/JSチェックの利用 - 型を書くにはJSDocを利用する
-
@type
: 汎用的な型宣言に利用 -
@typedef
: 型の命名
-
- 型宣言ファイルの利用
-
@type
を利用して、インテリセンスから一発で呼び出せる
-
//@ts-check
によるチェックは大変効果があります。一度JavaScriptファイルの先頭に記述してみてください。
どうしてもエラーが出て、記述で回避できない場合は、エラーが出る行の前に下記のコードを追加します。
//@ts-ignore
以上で、JavaScriptでの型の利用の概要を説明しました。
めんどくさい
ここまで読んで、「めんどくさい」と思われる方がいるかもしれません。しかし、実は型を利用したほうが、バグによる手戻りもなく、開発環境による入力支援を受けることができるので、開発スピードが向上します。
また大規模な開発になればなるほど、問題を分割して管理できるので、その効果が大きくなります。
みなさんも型を利用して安全で高速な開発へ一歩踏み出してみてください。
こちらもどうぞ。
具体的な型の記法を解説しています。
https://qiita.com/IshigiwaKenichiro/items/dbbbd0d6bee111cc48ca
参考文献
https://www.typescriptlang.org/ja/docs/handbook/jsdoc-supported-types.html
https://www.typescriptlang.org/ja/docs/handbook/intro-to-js-ts.html
この記事は以上です。ありがとうございました。
kintoneのプラグイン開発や研修などを行っています。
お仕事のお話はこちらまで。