はじめに
こんにちは!
現在、昔のコードを見直しているのですが、どのように型を定義したらいいか迷って、そのたびに調べたりしていました。型定義の書き方については、もうだいぶ昔に以下のような記事を書いたことがあるのですが
今回は JSDoc 向けに絞って、もっといろいろな書き方を紹介します。
型定義テクニック
JavaScriptは動的型付け言語ですが、VSCodeの『jsconfig』と『JSDoc』を組み合わせることで型情報を付加し、開発を円滑に進めることができます。
設定ファイル
最初に私の jsconfig.json を紹介します。checkJsをtrueにして型推論させます。
{
"buildOnSave": false, // 保存時にビルドしない
"compileOnSave": false, // 保存時にコンパイルしない
// コンパイルオプション
"compilerOptions": {
"ignoreDeprecations": "5.0",
"newLine": "LF", // 生成コードの改行コードを指定する
"lib": ["es2015", "dom"], // コンパイルに含めるライブラリの指定
"module": "es2015", // モジュールの方式
"target": "es2015", // 出力する ECMAScript のバージョン
"checkJs": true, // JavaScript ファイルの型整合性などをチェック
"noImplicitAny": true, // 暗黙的な any をエラーにする
"noEmit": true, // emit を作成しない
"moduleResolution": "Node"
},
"exclude": [
// 除外ファイル
"node_modules"
]
}
基本的な型の明示
JavaScriptでは変数の型が明示されないため、変数の内容が途中で変更されてしまう可能性があります。
const result = getValue();
型を明示することで問題を防げます。
/**
* @type {number}
*/
const result = getValue();
途中で値を受け取る際は、以下のよう記載することで一時的に型を指定できます。
const result = /** @type {number} */ (getValue());
変数を動的に扱うときのインデックスエラー
以下のようなコード
const params = { a: 1, b: 2, c: 3 };
for (const key in params) {
params[key] *= 2; // エラーが出る
}
次のようなエラーが発生します。
型 'string' の式を使用して型 '{ a: number; b: number; c: number; }' にインデックスを付けることはできないため、要素は暗黙的に 'any' 型になります。
型 'string' のパラメーターを持つインデックス シグネチャが型 '{ a: number; b: number; c: number; }' に見つかりませんでした。
@typedef {"a" | "b" | "c"} とすることで特定の文字列のみ許可する型となり、エラーは発生しません。
/** @typedef {"a" | "b" | "c"} ParamKey */
for (const key in params) {
const typedKey = /** @type {ParamKey} */ (key);
params[typedKey] *= 2;
}
既存クラスにプロパティを追加させる
例えば、Stringクラスに明示的に extra というプロパティを持ったの定義方法です。
/** @typedef {String & { extra: any }} ExtendedString */
/** @type {ExtendedString} */
const greeting = Object.assign(new String("Hello"), { extra: 123 });
{型A & 型B} と記載すると複数の型を結合するIntersection型を表します。これにより、既存の型と追加したいプロパティの両方を持つ型になります。
省略可能な引数を持つ関数の定義
引数が1つまたは2つの場合がある関数の定義方法です。
/** @type {function(number, string=): void} */
const myFunction = function(num, str) {
console.log(num, str);
};
{型=} と書くことで引数をオプショナルに設定できます。
ネストしたオブジェクトの型指定
プロパティ hoge が必ず存在するが、その中身のプロパティの必要性は任意というパターンの定義です。
/** @typedef {{ [key: string]: number }} StringNumberMap */
/** @typedef {{ hoge: StringNumberMap }} HogeObject */
/** @type {HogeObject} */
const obj = { hoge: { anyKey: 10 } };
型定義を分けることで、ネストした構造でも正しく型推論を作ります。また、{[key: string]: number} は自由なキーを持つオブジェクトで、その値は数値であることを示します。
nullを許可するプロパティ
文字列またはnullが格納されるプロパティの定義です。
/** @typedef {{ test: ?string }} NullableString */
/** @type {NullableString} */
const data = { test: null };
{?型} はnullable型を表し、その型またはnullを許容することになります。
複数の型が入る変数の宣言
Float32ArrayかInt32Arrayどちらかが入る型が入る変数の定義です。
/** @type {(typeof Float32Array | typeof Int32Array)} */
const TypedArrayClass = Float32Array;
typeof を付けることで型を表すようになり、また | を使うことで、どちらか一方の型を許可できます。
複数引数関数の一部だけ型指定
第1引数は数値、第2引数以降は自由といった関数の定義方法です。
/** @type {function(number, *): void} */
const func = function(first, ...args) {
console.log(first, args);
};
/**
* @type {function(number, *):void}
*/
const callback = (num, ...) => {};
任意型 * を使うことで、最初の引数だけを固定できます。
型情報だけを別ファイルにまとめる
typedefs.js に型をまとめ、別ファイルでimportします。
// typedefs.js
/**
* @typedef {Object} TypeA
*/
/**
* @typedef {Object} TypeB
*/
export default {};
型情報を使用したいソースコードの上部にて以下を記載します。
// 使用側ファイル
/** @typedef {import('./typedefs.js').TypeA} TypeA */
/** @typedef {import('./typedefs.js').TypeB} TypeB */
このように importを利用して再利用可能な型定義を行えます。
数値をキーに持つ連想配列
以下のように指定することで数値をキーとして明確にできます。
/**
* @type {Object<number, string>}
*/
const numMap = {};
numMap[1] = "value";
コールバック関数を明示する
@typedef で1行で関数型も書けますが、@callbackを使用して複数行に渡って定義することもできます。
/**
* @callback CallbackFunction
* @param {string} input
* @returns {void}
*/
/** @type {CallbackFunction} */
const cb = (input) => {};
このように、@callback は関数の引数と戻り値を分けて明示的に書ける、ドキュメント生成時にも「コールバック関数」として明示されるといったメリットがあります。
任意のプロパティが混在
[B] のように[]で囲むととプロパティが任意になります。
/**
* @typedef {Object} OptionalData
* @property {number} A
* @property {number} [B]
*/
これにより、必ず存在するプロパティA、任意のプロパティBといった定義も可能になります。
その他
前回の記事の内容で、今回の記事の中には載っていなかったものもあるので、ざっと紹介。
関数の定義
関数の場合は、以下のようになります。
/**
* ~する関数
* @param {number} x - 引数1
* @param {string} y - 引数2
* @returns {string}
*/
static myfunc(x, y) {
return "test";
}
paramの記載方法
型は次のように書くことが出来ます。
@param {number|string} x - 数値か文字列を引数とする。
@param {Array} x - 配列を引数とする。
@param {?Object} x - オブジェクトを引数とするが、nullを指定してもよい
@param {!Object} x - オブジェクトを引数とする。nullを指定してはいけない
@param {?(number|string)} x - 数値か文字列あるいは、nullを指定しても良い。
@param {number} [x] - 数値を引数とするが、省略してもよい。
@param {number} [x=10] - 数値を引数とするが、省略してもよい。省略した場合はデフォルトを10とする。
@param {{A: number, B: string}} x - AとBをメンバに持つオブジェクトを引数とする。
@param {number[]} x - 数値型の配列。
@param {Object<string, number>} x - キーを文字列、値を数値としたハッシュオブジェクト。
@param {...number} x - 数値型の可変引数
@param {typeof ClassX} x - ClassXの型を引数とする。
@param {function(number, string): boolean} func - 第1引数には数値、第2引数は文字列、戻り値はboolの関数
@param {import("./aaa/bbb.js").MyType} x - 他のファイルに記載してある型情報
おわりに
自由度があってしっかり型情報がかけて驚きですね。
最後まで見ていただきありがとうございました。





コメント