メインコンテンツへ移動

JSDoc サポート_TypeScript ノート 19

無料2019-05-12#TypeScript#TypeScript JSDoc#TypeScript JSDoc extends#JSDoc跨文件引用类型#JSDoc泛型#JSDoc inline typedef

TypeScript は一部の JSDoc マークのみをサポートしますが、タイプ注釈構文は JSDoc のスーパーセットです

一.JSDoc とタイプチェック

.js ファイル内では TypeScript タイプ注釈構文はサポートされていません:

// 错误 'types' can only be used in a .ts file.
let x: number;

したがって、.js ファイルに対して、JavaScript 構文と互換性のあるタイプ注釈方式が必要 です。例えば JSDoc

/** @type {number} */
let x;
// 错误 Type '"string"' is not assignable to type 'number'.
x = 'string';

この特殊な形式(/** で始まる)のコメントを通じてタイプを表現し、それによって JavaScript 構文と互換します。TypeScript タイプシステムはこれらの JSDoc マークを解析して追加のタイプ情報入力を取得し、タイプ推論と結合して .js ファイルのタイプチェックを行います

P.S. .js タイプチェックに関するより多くの情報については、[JavaScript ファイルのチェック_TypeScript ノート 18](/articles/检查 javascript 文件-typescript 笔记 18/) を参照

二.サポート程度

TypeScript は現在(2019/5/12)一部の JSDoc マークのみをサポート しています。具体的には:

  • @type:オブジェクトを記述

  • @param(または @arg または @argument):関数パラメータを記述

  • @returns(または @return):関数戻り値を記述

  • @typedef:カスタムタイプを記述

  • @callback:コールバック関数を記述

  • @class(または @constructor):該関数は new キーワードを通じて呼び出すべきであることを示す

  • @this:此处 this の指向を記述

  • @extends(または @augments):継承関係を記述

  • @enum:一組の関連属性を記述

  • @property(または @prop):オブジェクト属性を記述

P.S.完全な JSDoc マークリストは Block Tags を参照

特別に、ジェネリックに対して、JSDoc には適切なマークが提供されていません。そのため、追加のマークを拡張しました:

  • @template:ジェネリックを記述

P.S. @template マークでジェネリックを記述するのは Google Closure Compiler から由来し、より多くの関連討論は Add support for @template JSDoc を参照

三.タイプ注釈構文

TypeScript は JSDoc タイプ注釈と互換し、同時に JSDoc マーク中で TypeScript タイプ注釈構文を使用することもサポートします:

The meaning is usually the same, or a superset, of the meaning of the tag given at usejsdoc.org.

そうです、またスーパーセット です。そのため any タイプには 3 種類の注釈方式があります:

// JSDoc タイプ注釈構文
/** @type {*} - can be 'any' type */
var star = true;
/** @type {?} - unknown type (same as 'any') */
var question = true;

// どちらも TypeScript タイプ注釈構文と同等
/** @type {any} */
var thing = true;

構文方面では、JSDoc は主に Google Closure Compiler タイプ注釈 から借用しており、TypeScript には独自の [タイプ構文](/articles/基本类型-typescript 笔记 2/) があるため、二者にはいくつかの差異が存在します

タイプ宣言

@typedef マークを使用してカスタムタイプを宣言します。例えば:

/**
 * @typedef {Object} SpecialType - creates a new type named 'SpecialType'
 * @property {string} prop1 - a string property of SpecialType
 * @property {number} prop2 - a number property of SpecialType
 * @property {number=} prop3 - an optional number property of SpecialType
 * @prop {number} [prop4] - an optional number property of SpecialType
 * @prop {number} [prop5=42] - an optional number property of SpecialType with default
 */

/** @type {SpecialType} */
var specialTypeObject;

以下の TypeScript コードと同等です:

type SpecialType = {
  prop1: string;
  prop2: number;
  prop3?: number;
  prop4?: number;
  prop5?: number;
}
let specialTypeObject: SpecialType;

タイプ参照

@type マークを通じてタイプ名を参照します。タイプ名は基本タイプでも、TypeScript 宣言ファイル(d.ts)内に定義されているタイプでも、JSDoc マーク @typedef を通じて定義されたタイプでも構いません

例えば:

// 基本タイプ
/**
 * @type {string}
 */
var s;
/** @type {number[]} */
var ns;
/** @type {Array.<number>} */
var nds;
/** @type {Array<number>} */
var nas;
/** @type {Function} */
var fn7;
/** @type {function} */
var fn6;

// 外部宣言ファイル中に定義されたタイプ
/** @type {Window} */
var win;
/** @type {PromiseLike<string>} */
var promisedString;
/** @type {HTMLElement} */
var myElement = document.querySelector('#root');
element.dataset.myData = '';

// JSDoc @typedef で定義されたタイプ
/** @typedef {(data: string, index?: number) => boolean} Predicate */
/** @type Predicate */
var p;
p('True or not ?');

オブジェクトタイプもオブジェクトリテラルを通じて記述し、インデックス署名も同様に適用されます:

/** @type {{ a: string, b: number }} */
var obj;
obj.a.toLowerCase();

/**
 * 文字列インデックス署名
 * @type {Object.<string, number>}
 */
var stringToNumber;
// 同等
/** @type {{ [x: string]: number; }} */
var stringToNumber;

// 数値インデックス署名
/** @type {Object.<number, object>} */
var arrayLike;
// 同等
/** @type {{ [x: number]: any; }} */
var arrayLike;

関数タイプにも 2 種類の構文が選択可能です:

/** @type {function(string, boolean): number} Closure syntax */
var sbn;
/** @type {(s: string, b: boolean) => number} Typescript syntax */
var sbn2;

前者は形式パラメータ名を省略でき、後者は function キーワードを省略でき、意味は同じです

同様にタイプ組合もサポートします:

// ユニオンタイプ(JSDoc タイプ構文)
/**
 * @type {(string | boolean)}
 */
var sb;

// ユニオンタイプ(TypeScript タイプ構文)
/**
 * @type {string | boolean}
 */
var sb;

二者は同等で、構文にわずかな差異があるだけです

クロスファイルタイプ参照

特別に、import を通じて_他のファイル中に定義されたタイプを参照_ できます:

// a.js
/**
 * @typedef Pet
 * @property name {string}
 */
module.exports = {/* ... */};

// index.js
// 1. タイプを参照
/**
 * @param p { import("./a").Pet }
 */
function walk(p) {
  console.log(`Walking ${p.name}...`);
}

// 1. タイプを参照し、同時にエイリアスを付ける
/**
 * @typedef { import("./a").Pet } Pet
 */
/**
 * @type {Pet}
 */
var myPet;
myPet.name;

// 3. 推論されたタイプを参照
/**
 * @type {typeof import("./a").x }
 */
var x = require("./a").x;

注意、この構文は TypeScript 特有のものです(JSDoc はサポートしていません)。JSDoc 中では ES Module 導入構文を採用します:

// a.js
/**
 * @typedef State
 * @property {Array} layers
 * @property {object} product
 */

// index.js
import * as A from './a';
/** @param {A.State} state */
const f = state => ({
  product: state.product,
  layers: state.layers,
});

この方式は実際の import を追加します。もし純粋なタイプ宣言ファイル( @typedef のみの .jsd.ts に類似)であれば、JSDoc 方式は無用なファイル(コメントのみを含む)を導入することになりますが、TypeScript 方式にはこの問題が存在しません

P.S.TypeScript は同時にこれら 2 種類のタイプ導入構文と互換します。より多くの関連討論は Question: Import typedef from another file? を参照

タイプ変換

タイプ変換(TypeScript 中の [タイプアサーション](/articles/基本类型-typescript 笔记 2/#articleHeader4))構文は JSDoc と一致し、丸括弧前の @type マークを通じて丸括弧内の式タイプを説明します:

/** @type {!MyType} */ (valueExpression)

例えば:

/** @type {number | string} */
var numberOrString = Math.random() < 0.5 ? "hello" : 100;
var typeAssertedNumber = /** @type {number} */ (numberOrString)

// 错误 Type '"hello"' is not assignable to type 'number'.
typeAssertedNumber = 'hello';

P.S.注意、丸括弧が必須 です。否则認識されません

四.一般的なタイプ

オブジェクト

一般に @typedef マークを使用してオブジェクトタイプを記述します。例えば:

/**
 * The complete Triforce, or one or more components of the Triforce.
 * @typedef {Object} WishGranter~Triforce
 * @property {boolean} hasCourage - Indicates whether the Courage component is present.
 * @property {boolean} hasPower - Indicates whether the Power component is present.
 * @property {boolean} hasWisdom - Indicates whether the Wisdom component is present.
 */

TypeScript タイプと同等です:

interface WishGranter {
  hasCourage: boolean;
  hasPower: boolean;
  hasWisdom: boolean;
}
// または
type WishGranter = {
  hasCourage: boolean;
  hasPower: boolean;
  hasWisdom: boolean;
}

もし一回限りのタイプ宣言(再利用不要、追加でタイプを定義したくない)であれば、 @param マークを使用して宣言できます。options.prop1 形式の属性名を通じてメンバー属性のネスト関係を記述します:

/**
 * @param {Object} options - The shape is the same as SpecialType above
 * @param {string} options.prop1
 * @param {number} options.prop2
 * @param {number=} options.prop3
 * @param {number} [options.prop4]
 * @param {number} [options.prop5=42]
 */
function special(options) {
  return (options.prop4 || 1001) + options.prop5;
}

関数

@typedef マークでオブジェクトを記述するのと同様に、@callback マークを使用して関数のタイプを記述できます:

/**
 * @callback Predicate
 * @param {string} data
 * @param {number} [index]
 * @returns {boolean}
 */

/** @type {Predicate} */
const ok = s => !(s.length % 2);

TypeScript コードと同等です:

type Predicate = (data: string, index?: number) => boolean

また @typedef 特殊構文(TypeScript のみサポート、JSDoc にはありません)を使用してオブジェクトまたは関数のタイプ定義を 1 行に統合できます:

/** @typedef {{ prop1: string, prop2: string, prop3?: number }} SpecialType */
/** @typedef {(data: string, index?: number) => boolean} Predicate */

// TypeScript コードと同等
type SpecialType = {
  prop1: string;
  prop2: string;
  prop3?: number;
}
type Predicate = (data: string, index?: number) => boolean

パラメータ

関数パラメータは @param マークを通じて記述し、@type 構文と同じですが、パラメータ名が 1 つ追加されます。例えば:

/**
 * @param {string} p1 必須パラメータ
 */
function f(p1) {}

オプションパラメータには 3 種類の表現方式があります:

/**
 * @param {string=} p1 - オプションパラメータ(Closure 構文)
 * @param {string} [p2] - オプションパラメータ(JSDoc 構文)
 * @param {string} [p3 = 'test'] - デフォルト値のあるオプションパラメータ(JSDoc 構文)
 */
function fn(p1, p2, p3) {}

P.S.注意、接尾等号構文({string=} など)はオブジェクトリテラルタイプには適用されません。例えば @type {{ a: string, b: number= }} は非法なタイプ宣言で、オプション属性は属性名接尾 ? で表現すべきです

可変長パラメータには 2 種類の表現方式があります:

/**
 * @param {...string} p - A 'rest' arg (array) of strings. (treated as 'any')
 */
function fn(p){ arguments; }

/** @type {(...args: any[]) => void} */
function f() { arguments; }

戻り値

戻り値のタイプ注釈方式も類似しています:

/**
 * @return {PromiseLike<string>}
 */
function ps() {
  return Promise.resolve('');
}
/**
 * @returns {{ a: string, b: number }}
 */
function ab() {
  return {a: 'a', b: 11};
}

P.S. @returns@return は完全に同等で、後者は前者のエイリアスです

クラス

コンストラクタ

タイプシステムは this への属性代入に基づいてコンストラクタを推論し、 @constructor マークを通じてコンストラクタを記述することもできます

二者の違いは @constructor マークがある場合、タイプチェックがより厳格 になります。具体的には、コンストラクタ中の this 属性アクセスおよびコンストラクタパラメータをチェックし、(new キーワードを通じずに)コンストラクタを直接呼び出すことを許可しません:

/**
 * @constructor
 * @param {number} data
 */
function C(data) {
  this.size = 0;
  // 错误 Argument of type 'number' is not assignable to parameter of type 'string'.
  this.initialize(data);
}
/**
 * @param {string} s
 */
C.prototype.initialize = function (s) {
  this.size = s.length
}

var c = new C(0);
// 错误 Value of type 'typeof C' is not callable. Did you mean to include 'new'?
var result = C(1);

P.S. @constructor マークを削除すれば、これら 2 つのエラーは報告されません

さらに、コンストラクタまたはクラスタイプのパラメータに対して、TypeScript 構文に類似した方式でそのタイプを記述できます:

/**
 * @template T
 * @param {{new(): T}} C コンストラクタ C が必ず同一クラス(またはサブクラス)のインスタンスを返すことを要求
 * @returns {T}
 */
function create(C) {
  return new C();
}

P.S.JSDoc は Newable パラメータを記述する方式を提供していません。詳細は Document class types/constructor types を参照

this タイプ

ほとんどの場合、タイプシステムはコンテキストに基づいて this のタイプを推論できます。複雑なシナリオでは @this マークを通じて this のタイプを明示的に指定できます:

// 推論タイプは function getNodeHieght(): any
function getNodeHieght() {
  return this.innerHeight;
}

// this タイプを明示的に指定、推論タイプは function getNodeHieght(): number
/**
 * @this {HTMLElement}
 */
function getNodeHieght() {
  return this.clientHeight;
}

継承

TypeScript では、クラスの継承関係は JSDoc を通じて記述できません

class Animal {
  alive = true;
  move() {}
}
/**
 * @extends {Animal}
 */
class Duck {}

// 错误 Property 'move' does not exist on type 'Duck'.
new Duck().move();

@augments(または @extends)は基底クラスのジェネリックパラメータを指定するためにのみ使用されます:

/**
 * @template T
 */
class Box {
  /**
  * @param {T} value
  */
  constructor(value) {
    this.value = value;
  }
  unwrap() {
    return this.value;
  }
}
/**
 * @augments {Box<string>} 記述
 */
class StringBox extends Box {
  constructor() {
    super('string');
  }
}

new StringBox().unwrap().toUpperCase();

しかし JSDoc と不同的是、 @arguments/extends マークはClass のみに使用可能で、コンストラクタには適用されません:

/**
 * @constructor
 */
function Animal() {
  this.alive = true;
}

/**
 * @constructor
 * @augments Animal
 */
// 错误 JSDoc ' @augments' is not attached to a class.
function Duck() {}
Duck.prototype = new Animal();

したがって、 @augments/extends マークの作用は非常に弱く、非 Class 継承を記述できず、継承関係を決定することもできません(継承関係は extends 句によって決定され、JSDoc の記述はカウントされません)

列挙

列挙は @enum マークを使用して記述しますが、TypeScript 列挙タイプ とは異なり、主な差異は:

  • 列挙メンバーのタイプが一致することを要求

  • しかし列挙メンバーは任意のタイプ可以是

例えば:

/** @enum {number} */
const JSDocState = {
  BeginningOfLine: 0,
  SawAsterisk: 1,
  SavingComments: 2,
}

/** @enum {function(number): number} */
const SimpleMath = {
  add1: n => n + 1,
  id: n => n,
  sub1: n => n - 1,
}

ジェネリック

ジェネリックは @template マークを使用して記述します:

/**
* @template T
* @param {T} x - A generic parameter that flows through to the return type
* @return {T}
*/
function id(x) { return x }

let x = id('string');
// 错误 Type '0' is not assignable to type 'string'.
x = 0;

TypeScript コードと同等です:

function id<T>(x: T): T {
  return x;
}

let x = id('string');
x = 0;

複数の [タイプパラメータ](/articles/泛型-typescript 笔记 6/#articleHeader3) がある場合、カンマで区切るか、複数の @template タグを使用できます:

/**
 * @template T, U
 * @param {[T, U]} pairs 二項組
 * @returns {[U, T]}
 */
function reversePairs(pairs) {
  const x = pairs[0];
  const y = pairs[1];
  return [y, x];
}

// 同等
/**
 * @template T
 * @template U
 * @param {[T, U]} pairs 二項組
 * @returns {[U, T]}
 */
function reversePairs(pairs) {
  const x = pairs[0];
  const y = pairs[1];
  return [y, x];
}

さらに、[ジェネリック制約](/articles/泛型-typescript 笔记 6/#articleHeader8) もサポートします:

/**
 * @typedef Lengthwise
 * @property length {number}
 */

/**
 * @template {Lengthwise} T
 * @param {T} arg
 * @returns {T}
 */
function loggingIdentity(arg) {
  console.log(arg.length);  // Now we know it has a .length property, so no more error
  return arg;
}

TypeScript コードと同等です:

interface Lengthwise {
  length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
  console.log(arg.length);  // Now we know it has a .length property, so no more error
  return arg;
}

特別に、 @typedef マークと結合してジェネリックタイプを定義する際、必ず先にジェネリックパラメータを定義 する必要があります:

/**
 * @template K
 * @typedef Wrapper
 * @property value {K}
 */

/** @type {Wrapper<string>} */
var s;
s.value.toLocaleLowerCase();

@template@typedef の順序は逆転できません。否则 エラーが報告されます:

JSDoc ' @typedef' tag should either have a type annotation or be followed by ' @property' or ' @member' tags.

TypeScript ジェネリック宣言と同等です:

type Wrapper<K> = {
  value: K;
}

Nullable

JSDoc 中では、明示的に Null 可能タイプと非 Null タイプを指定できます。例えば:

  • {?number}number | null を表す

  • {!number}number を表す

しかし TypeScript 中では明示的に指定できません。タイプが Null を含むかどうかは --strictNullChecks オプションのみに依存 します:

/**
 * @type {?number}
 * strictNullChecks を开启時、タイプは number | null
 * strictNullChecks を关闭時、タイプは number
 */
var nullable;

/**
 * @type {!number} 明示的に非 Null を指定しても無効、strictNullChecks オプションのみに依存
 */
var normal;

参考資料

コメント

コメントはまだありません

コメントを書く