首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JSDoc支持_TypeScript笔记19

JSDoc支持_TypeScript笔记19

作者头像
ayqy贾杰
发布2019-06-12 15:19:27
4K0
发布2019-06-12 15:19:27
举报
文章被收录于专栏:黯羽轻扬黯羽轻扬黯羽轻扬

一.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

二.支持程度

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 则有自己的一套类型语法,因此二者存在一些差异

类型声明

@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;

函数类型也有两种语法可选:

/** @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.js,类似于d.ts),JSDoc 方式会引入一个无用文件(只含有注释),而 TypeScript 方式则不存在这个问题

P.S.TypeScript 同时兼容这两种类型引入语法,更多相关讨论见Question: Import typedef from another file?

类型转换

类型转换(TypeScript 里的类型断言)语法与 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 里没有)把对象或函数的类型定义整合到一行:

/** @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语法相同,只是增加了一个参数名,例如:

/**
 * @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标记的话,不会报出这两个错误

另外,对于构造函数或类类型的参数,可以通过类似于 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;

有多个类型参数时,可以用逗号隔开,或者用多个@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];
}

此外,还支持泛型约束:

/**
 * @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;

参考资料

  • Type Checking JavaScript Files
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-05-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 前端向后 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一.JSDoc 与类型检查
  • 二.支持程度
  • 三.类型标注语法
    • 类型声明
      • 类型引用
        • 跨文件类型引用
      • 类型转换
      • 四.常见类型
        • 对象
          • 函数
            • 参数
            • 返回值
            • 构造函数
            • this 类型
            • 继承
          • 枚举
            • 泛型
              • Nullable
                • 参考资料
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档