[译]<<Effective TypeScript>> 技巧59:使用@ts-check 和 JSDoc提前尝试ts

300 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第6天,点击查看活动详情

本文的翻译于<<Effective TypeScript>>, 特别感谢!! ps: 本文会用简洁, 易懂的语言描述原书的所有要点. 如果能看懂这文章,将节省许多阅读时间. 如果看不懂,务必给我留言, 我回去修改.

技巧59:使用@ts-check 和 JSDoc提前尝试ts

在你将你的js代码转换成ts之前,你可能想提前尝试ts,看自己的js代码会出哪些错误。@ts-check会对单个文件执行检查,并且报告所有会出现的错误:

// @ts-check
const person = {first: 'Grace', last: 'Hopper'};
2 * person.first
 // ~~~~~~~~~~~~ The right-hand side of an arithmetic operation must be of type
 //              'any', 'number', 'bigint', or an enum type

person.first类型推断为string,所以 2 * person.first会报类型错误。

除了一些明显的类型错误,在实践中,@ts-check还会报一些特定的错误:

未申明的全局变量

如果你想声明一个变量,可以使用let,const关键字。如果是其他地方定义的环境变量(例如<script>标签里面定义的),你可以创建一个类型申明文件去声明他们:

例如,你的js文件像这样:

/ @ts-check
console.log(user.firstName);
         // ~~~~ Cannot find name 'user'

你可以新建一个types.d.ts

interface UserData {
  firstName: string;
  lastName: string;
}
declare let user: UserData;

创建这个文件可以修复这个错误,如果不行。你再添加一个「triple slash」引用

// @ts-check
/// <reference path="./types.d.ts" />
console.log(user.firstName);  // OK

types.d.ts文件会成为你项目的基础的类型声明文件。

未知的库

如果你是用第三方的库,ts需要了解这个库。例如你正在使用jQuery来设置HTML元素的size,使用@ts-check会报这样的错误:

/ @ts-check
   $('#graph').style({'width': '100px', 'height': '100px'});
// ~ Cannot find name '$'

解决办法是按照jQuery的@types:

$ npm install --save-dev @types/jquery

现在jQuery的错误更加具体了:

/ @ts-check
$('#graph').style({'width': '100px', 'height': '100px'});
         // ~~~~~ Property 'style' does not exist on type 'JQuery<HTMLElement>'

实际上,应该是.css而不是.style

@ts-check能让你享受ts的好处,但又不用转成ts。你更应该试试它

DOM Issues

当你写的代码运行在web浏览器上,在你处理DOM元素的时候,ts很有可能报错。

/ @ts-check
const ageEl = document.getElementById('age');
ageEl.value = '12';
   // ~~~~~ Property 'value' does not exist on type 'HTMLElement'

问题出在:只有HTMLInputElement有value属性,但是document.getElementById返回的是更通用的HTMLElement(具体见技巧55)。 如果你知道#age元素是HTMLInputElement,你可以使用类型断言,但这不是ts文件,在@ts-check中可以这么做:

// @ts-check
const ageEl = /** @type {HTMLInputElement} */(document.getElementById('age'));
ageEl.value = '12';  // OK

不准确的JSDoc

如果你的代码已经有JSDoc风格的注释,那么当有一些「野心勃勃」但是又不准确的JSDoc时,可能会出错:

// @ts-check
/**
 * Gets the size (in pixels) of an element.
 * @param {Node} el The element
 * @return {{w: number, h: number}} The size
 */
function getSize(el) {
  const bounds = el.getBoundingClientRect();
                 // ~~~~~~~~~~~~~~~~~~~~~ Property 'getBoundingClientRect'
                 //                       does not exist on type 'Node'
  return {width: bounds.width, height: bounds.height};
       // ~~~~~~~~~~~~~~~~~~~ Type '{ width: any; height: any; }' is not
       //                     assignable to type '{ w: number; h: number; }'
}

第一个误会是getBoundingClientRect()在Element上,而不是Node上。所以@param tag 应该升级。第二个误会是@return tag的声明和实现不一致。

你可以使用JSDoc逐步给你的项目添加类型声明:

function double(val) {
  return 2 * val;
}

image.png 你可以这样添加类型:

// @ts-check
/**
 * @param {number} val
 */
function double(val) {
  return 2 * val;
}

@ts-check鼓励你让type在你的代码中流动。但是并不是每次都有效:

function loadData(data) {
  data.files.forEach(async file => {
    // ...
  });
}

当你想快速修复这个错误,你可能会这样做:

/**
 * @param {{
 *  files: { forEach: (arg0: (file: any) => Promise<void>) => void; };
 * }} data
 */
function loadData(data) {
  // ...
}

虽然可以从技术上修复这个错误,但是代码的目的很可能是:{files: string[]}

@ts-check和JSDoc能让你提前享受ts的好处,但是这也有成本的:很容易让你逻辑迷失在JSDoc的大海中。能尽量使用ts就使用ts。@ts-check和JSDoc只是你提前尝试ts的工具